Ответ 1
Это классическая проблема с асинхронными веб-API. Вы не можете вернуть то, что еще не было загружено. Другими словами, вы не можете просто создать глобальную переменную и использовать ее вне onDataChange()
потому что она всегда будет null
. Это происходит потому, что onDataChange()
называется асинхронным. В зависимости от скорости вашего соединения и состояния, эти данные могут занять от нескольких сотен миллисекунд до нескольких секунд.
Но не только Firebase Realtime Database загружает данные асинхронно, почти все другие современные веб-API делают, так как это может занять некоторое время. Таким образом, вместо ожидания данных (что может привести к не отвечающим диалогам приложений для ваших пользователей), ваш основной код приложения продолжается, пока данные загружаются во вторичный поток. Затем, когда данные доступны, вызывается ваш метод onDataChange(), который может использовать данные. Другими словами, к моменту onDataChange()
метода onDataChange()
ваши данные еще не загружены.
Давайте возьмем пример, разместив несколько операторов журнала в коде, чтобы более четко увидеть, что происходит.
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!");
}
Если мы запустим этот код, результат будет:
Прежде чем прикрепить слушателя!
После прикрепления слушателя!
Внутри метода 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()
. Для получения дополнительной информации вы также можете посмотреть это видео.