How to return DataSnapshot value as a result of a method?
Как вернуть значение DataSnapshot в результате метода?
У меня нет большого опыта работы с Java. Я не уверен, что этот вопрос глупый, но мне нужно получить имя пользователя из базы данных Firebase realtime database и вернуть это имя в результате использования этого метода. Итак, я понял, как получить это значение, но я не понимаю, как вернуть его в результате этого метода. Какой лучший способ сделать это?
private String getUserName(String uid) { databaseReference.child(String.format("users/%s/name", uid)) .addListenerForSingleValueEvent(newValueEventListener() { @Override publicvoidonDataChange(DataSnapshot dataSnapshot) { // How to return this value? dataSnapshot.getValue(String.class); }
Это классическая проблема с асинхронными веб-API. Теперь вы не можете вернуть что-то, что еще не было загружено. Другими словами, вы не можете просто создать глобальную переменную и использовать ее вне onDataChange() метода, потому что так будет всегда null. Это происходит потому, что onDataChange() метод называется асинхронным. В зависимости от скорости вашего соединения и состояния, может потребоваться от нескольких сотен миллисекунд до нескольких секунд, прежде чем эти данные станут доступны.
Но не только база данных Firebase в реальном времени загружает данные асинхронно, но и почти все современные веб-API также загружают, поскольку это может занять некоторое время. Таким образом, вместо ожидания данных (что может привести к невосприимчивым диалоговым окнам приложения для ваших пользователей), код вашего основного приложения продолжается, пока данные загружаются во вторичный поток. Затем, когда данные доступны, вызывается ваш метод onDataChange(), который может использовать данные. Другими словами, к моменту вызова onDataChange() метода ваши данные еще не загружены.
Давайте рассмотрим пример, поместив в код несколько операторов log, чтобы более четко видеть, что происходит.
private String getUserName(String uid) { Log.d("TAG", "Before attaching the listener!"); databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(newValueEventListener() { @Override publicvoidonDataChange(DataSnapshot dataSnapshot) { // How to return this value? dataSnapshot.getValue(String.class); Log.d("TAG", "Inside onDataChange() method!"); }
Если мы запустим этот код will, результатом будет:
Перед подключением прослушивателя!
После подключения прослушивателя!
Внутри метода onDataChange()!
Вероятно, это не то, что вы ожидали, но это точно объясняет, почему ваши данные являются null при их возврате.
Первоначальная реакция большинства разработчиков - попытаться "исправить" это asynchronous behavior, что я лично рекомендую против этого. Веб является асинхронным, и чем раньше вы с этим согласитесь, тем скорее сможете научиться работать продуктивно с современными веб-API.
Я обнаружил, что проще всего переформулировать проблемы для этой асинхронной парадигмы. Вместо того, чтобы говорить "Сначала получите данные, затем запишите их", я формулирую проблему как "Начинаю получать данные. Когда данные загружены, запишите их в журнал ". Это означает, что любой код, требующий данных, должен находиться внутри onDataChange() метода или вызываться изнутри оттуда, вот так:
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(newValueEventListener() { @Override publicvoidonDataChange(DataSnapshot dataSnapshot) { // How to return this value? if(dataSnapshot != null) { System.out.println(dataSnapshot.getValue(String.class)); } }
Если вы хотите использовать это снаружи, есть другой подход. Вам нужно создать свой собственный обратный вызов, чтобы дождаться, пока Firebase вернет вам данные. Для достижения этого сначала вам нужно создать interface подобное этому:
Это единственный способ, которым вы можете использовать это значение вне onDataChange() метода. Для получения дополнительной информации вы также можете посмотреть это видео.
Редактировать: 26 февраля 2021 г.
Для получения дополнительной информации вы можете ознакомиться со следующей статьей:
Начиная с версии "19.6.0", Firebase Realtime Database SDK содержит новый метод с именем get(), который может вызываться либо для DatabaseReference, либо для объекта запроса:
Добавлены DatabaseReference#get() и Query#get(), которые возвращают данные с сервера, даже если более старые данные доступны в локальном кэше.
Как мы уже знаем, Firebase API является асинхронным. Поэтому нам нужно создать для этого обратный вызов. Сначала давайте создадим интерфейс:
Полагаю, я понимаю, о чем вы спрашиваете. Хотя вы говорите, что хотите "вернуть" его (как таковое) из метода выборки, может быть достаточно сказать, что на самом деле вы просто хотите иметь возможность использовать значение, полученное после завершения выборки. Если да, то это то, что вам нужно сделать:
Создайте переменную в верхней части вашего класса
Извлеките ваше значение (что вы сделали в основном правильно)
Установите общедоступную переменную в вашем классе равной полученному значению
После успешной выборки вы можете многое сделать с переменной. 4a и 4b - несколько простых примеров.:
4a. Редактировать: В качестве примера использования вы можете запустить все остальное, что вам нужно для запуска в вашем классе, который использует yourNameVariable (и вы можете быть уверены, что это yourNameVariable не null)
4b. Редактировать: В качестве примера использования вы можете использовать переменную в функции, которая запускается кнопкой OnClickListener.
Попробуйте это.
// 1. Create a variable at the top of your class private String yourNameVariable;
// 2. Retrieve your value (which you have done mostly correctly) privatevoidgetUserName(String uid) { databaseReference.child(String.format("users/%s/name", uid)) .addListenerForSingleValueEvent(newValueEventListener() { @Override publicvoidonDataChange(DataSnapshot dataSnapshot) { // 3. Set the public variable in your class equal to value retrieved yourNameVariable = dataSnapshot.getValue(String.class); // 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null. sayHiToMe(); }
// 4b. use the variable in a function triggered by the onClickListener of a button. publicvoidhelloButtonWasPressed() { if (yourNameVariable != null) { Log.d(TAG, "hi there, " + yourNameVariable); } }
Затем вы можете использовать yourNameVariable везде, где захотите, в своем классе.
Примечание: просто убедитесь, что вы проверяете, что yourNameVariable не равно null при его использовании, поскольку onDataChange является асинхронным и может не завершиться при попытке использовать его в другом месте.
Ответ 4
Вот сумасшедшая идея, внутри onDataChange поместить его в TextView с пропавшей видимостью textview.setVisiblity(Gone) или что-то в этом роде, затем сделать что-то вроде