Android

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(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
}

@Override
public void onCancelled(DatabaseError databaseError) {}
});
}
Переведено автоматически
Ответ 1

Это классическая проблема с асинхронными веб-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(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
dataSnapshot.getValue(String.class);
Log.d("TAG", "Inside onDataChange() method!");
}

@Override
public void onCancelled(DatabaseError databaseError) {}
});
Log.d("TAG", "After attaching the listener!");
}

Если мы запустим этот код will, результатом будет:


Перед подключением прослушивателя!


После подключения прослушивателя!


Внутри метода onDataChange()!


Вероятно, это не то, что вы ожидали, но это точно объясняет, почему ваши данные являются null при их возврате.

Первоначальная реакция большинства разработчиков - попытаться "исправить" это asynchronous behavior, что я лично рекомендую против этого. Веб является асинхронным, и чем раньше вы с этим согласитесь, тем скорее сможете научиться работать продуктивно с современными веб-API.

Я обнаружил, что проще всего переформулировать проблемы для этой асинхронной парадигмы. Вместо того, чтобы говорить "Сначала получите данные, затем запишите их", я формулирую проблему как "Начинаю получать данные. Когда данные загружены, запишите их в журнал ". Это означает, что любой код, требующий данных, должен находиться внутри onDataChange() метода или вызываться изнутри оттуда, вот так:

databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// How to return this value?
if(dataSnapshot != null) {
System.out.println(dataSnapshot.getValue(String.class));
}
}

@Override
public void onCancelled(DatabaseError databaseError) {}
});

Если вы хотите использовать это снаружи, есть другой подход. Вам нужно создать свой собственный обратный вызов, чтобы дождаться, пока Firebase вернет вам данные. Для достижения этого сначала вам нужно создать interface подобное этому:

public interface MyCallback {
void onCallback(String value);
}

Затем вам нужно создать метод, который фактически получает данные из базы данных. Этот метод должен выглядеть следующим образом:

public void readData(MyCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
myCallback.onCallback(value);
}

@Override
public void onCancelled(DatabaseError databaseError) {}
});
}

В конце просто вызовите readData() метод и передайте экземпляр MyCallback интерфейса в качестве аргумента везде, где вам это нужно, вот так:

readData(new MyCallback() {
@Override
public void onCallback(String value) {
Log.d("TAG", value);
}
});

Это единственный способ, которым вы можете использовать это значение вне onDataChange() метода. Для получения дополнительной информации вы также можете посмотреть это видео.

Редактировать: 26 февраля 2021 г.

Для получения дополнительной информации вы можете ознакомиться со следующей статьей:

И следующее видео:

Ответ 2

Начиная с версии "19.6.0", Firebase Realtime Database SDK содержит новый метод с именем get(), который может вызываться либо для DatabaseReference, либо для объекта запроса:


Добавлены DatabaseReference#get() и Query#get(), которые возвращают данные с сервера, даже если более старые данные доступны в локальном кэше.


Как мы уже знаем, Firebase API является асинхронным. Поэтому нам нужно создать для этого обратный вызов. Сначала давайте создадим интерфейс:

public interface FirebaseCallback {
void onResponse(String name);
}

И метод, который принимает в качестве аргумента объект tye FirebaseCallback:

public void readFirebaseName(FirebaseCallback callback) {
DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
@Override
public void onComplete(@NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
String name = task.getResult().getValue(String.class);
callback.onResponse(name);
} else {
Log.d(TAG, task.getException().getMessage());
}
}
});
}

Теперь, чтобы прочитать данные, вам нужно просто вызвать вышеупомянутый метод, передав в качестве аргумента объект типа FirebaseCallback:

readFirebaseName(new FirebaseCallback() {
@Override
public void onResponse(String name) {
Log.d("TAG", name);
}
});

Для получения дополнительной информации вы можете ознакомиться со следующей статьей:

И следующее видео:

Ответ 3

Полагаю, я понимаю, о чем вы спрашиваете. Хотя вы говорите, что хотите "вернуть" его (как таковое) из метода выборки, может быть достаточно сказать, что на самом деле вы просто хотите иметь возможность использовать значение, полученное после завершения выборки. Если да, то это то, что вам нужно сделать:


  1. Создайте переменную в верхней части вашего класса

  2. Извлеките ваше значение (что вы сделали в основном правильно)

  3. Установите общедоступную переменную в вашем классе равной полученному значению

После успешной выборки вы можете многое сделать с переменной. 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)
private void getUserName(String uid) {
databaseReference.child(String.format("users/%s/name", uid))
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(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();
}

@Override
public void onCancelled(DatabaseError databaseError) {}
});
}

// (part of step 4a)
public void sayHiToMe() {
Log.d(TAG, "hi there, " + yourNameVariable);
}

// 4b. use the variable in a function triggered by the onClickListener of a button.
public void helloButtonWasPressed() {
if (yourNameVariable != null) {
Log.d(TAG, "hi there, " + yourNameVariable);
}
}

Затем вы можете использовать yourNameVariable везде, где захотите, в своем классе.


Примечание: просто убедитесь, что вы проверяете, что yourNameVariable не равно null при его использовании, поскольку onDataChange является асинхронным и может не завершиться при попытке использовать его в другом месте.

Ответ 4

Вот сумасшедшая идея, внутри onDataChange поместить его в TextView с пропавшей видимостью textview.setVisiblity(Gone) или что-то в этом роде, затем сделать что-то вроде

textview.setText(dataSnapshot.getValue(String.class))

затем позже получите его с помощью textview.getText().toString()

просто безумно простая идея.

java android firebase firebase-realtime-database