Android не может вернуть значения из Firebase Datasnapshot [duplicate]

Все объекты гарантированно имеют метод .equals(), поскольку Object содержит метод, .equals(), который возвращает логическое значение. Задача подкласса переопределять этот метод, если требуется дополнительное определение определения. Без него (т. Е. С помощью ==) только адреса памяти проверяются между двумя объектами для равенства. String переопределяет этот метод .equals() и вместо использования адреса памяти возвращает сравнение строк на уровне символа для равенства.

Ключевое замечание состоит в том, что строки хранятся в одном пуле, поэтому после создания строки он всегда хранится в программе по тому же адресу. Строки не меняются, они неизменяемы. Вот почему это плохая идея использовать регулярную конкатенацию строк, если у вас есть серьезное количество обработки строк. Вместо этого вы будете использовать предоставленные классы StringBuilder. Помните, что указатели на эту строку могут измениться, и если вам было интересно увидеть, были ли два указателя одинаковыми ==, это был бы прекрасный способ. Строки сами не делают.

11
задан Ilya Cucumber 16 December 2017 в 17:25
поделиться

3 ответа

Это классическая проблема с асинхронными веб-интерфейсами. Вы не можете вернуть то, что еще не загружено. Другими словами, вы не можете просто создать глобальную переменную и использовать ее вне метода onDataChange(), потому что она всегда будет null. Это происходит потому, что метод onDataChange() называется асинхронным.

Но не только Firebase Realtime Database загружает данные асинхронно, почти все современные другие сети API, так как это может занять некоторое время. Поэтому вместо того, чтобы ждать данных (что может привести к невосприимчивым диалоговым окнам приложений для ваших пользователей), ваш основной код приложения продолжается, пока данные загружаются во вторичный поток. Затем, когда данные доступны, вызывается метод 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!");
}

Если мы запустим этот код, выход wil будет:

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

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

Внутри метода 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(). Для получения дополнительной информации вы также можете посмотреть это видео .

32
ответ дан Alex Mamo 17 August 2018 в 09:25
поделиться
  • 1
    Может ли это быть достигнуто с использованием интерфейса Future и Callable? developer.android.com/reference/java/util/concurrent/Future – Peter Haddad 31 July 2018 в 16:46
  • 2
    Привет Питер! Это может сработать. Я еще не пробовал, но я это сделаю. Я не знаю, знаете ли вы это, но вот интересная статья . – Alex Mamo 31 July 2018 в 18:25
  • 3
    Да, я видел это раньше, так как он написан как комментарий во многих местах. Но я не читал его, потому что знаю, как асинхронно работает после прочтения пары Q & amp; A здесь. В любом случае, я прочитал его сейчас, и я думаю, что лучше не использовать Future, так как нам нужно использовать newSingleThreadExecutor(), который создает другой поток, и поскольку api асинхронен, то он уже находится в потоке в java, и мы должны также поддерживайте эту тему и пишите больше кода. – Peter Haddad 31 July 2018 в 19:43
  • 4
    Клиент @PeterHaddad Firebase уже выполняет все сетевые операции в фоновом потоке, поэтому я думаю, что вы правы. В этом нет необходимости. Спасибо, что предоставил мне этот ресурс! – Alex Mamo 31 July 2018 в 20:24

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

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

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

просто сумасшедшая простая идея.

0
ответ дан klaid bendio Moran 17 August 2018 в 09:25
поделиться

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

  1. Создать переменную в верхней части вашего класса
  2. Извлечь ваше значение (которое вы сделали в основном правильно)
  3. Установите общедоступную переменную в вашем классе, равную полученному значению

. После того, как ваша выборка завершится успешно, вы можете делать много вещей с переменной. 4а и 4b - некоторые простые примеры:

4a. Изменить. В качестве примера использования вы можете запустить все, что вам нужно для запуска в вашем классе, который использует yourNameVariable (и вы можете быть уверены, что yourNameVariable не является нулевым)

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 не является нулевым при его использовании, поскольку onDataChange является асинхронным и может не завершиться в то время, когда вы пытаетесь использовать его в другом месте.

4
ответ дан Rbar 17 August 2018 в 09:25
поделиться
  • 1
    Это неправильный ответ. Это не так, когда дело касается асинхронных методов. Используя этот ответ, вы всегда будете иметь переменную yourNameVariable всегда null при использовании & quot ;, где бы вы ни находились по всему классу & quot ;. Вы можете использовать его только в своем методе onDataChange(). Вот и все. – Alex Mamo 17 December 2017 в 10:32
  • 2
    @AlexMamo, ваше решение по определению интерфейса работает хорошо, но следующая часть вашего комментария неверна: & quot; yourNameVariable всегда null. & Quot; Для тех, кто ищет более легкое решение (которое не требует определения интерфейса), это работает хорошо. После того, как переменная была восстановлена ​​и установлена, они могут запускать любые функции, которые требуют результата, и знают, что переменная не является null (если она существует в firebase в первую очередь). Я обновил свой пример, чтобы вы могли увидеть конкретный прецедент. – Rbar 17 December 2017 в 20:08
  • 3
    Кроме того, вызов этого метода не имеет ничего общего с асинхронным поведением методов. Вызов этого метода аналогичен использованию этой линии Log.d(TAG, "hi there, " + yourNameVariable); внутри метода onDataChange(). Это означает, что при запуске метода onDataChange() вы вызываете этот метод. – Alex Mamo 18 December 2017 в 09:00
  • 4
    @AlexMamo, функция была всего лишь одним примером. Вы можете делать много других вещей, которые автоматически не запускают функционал. Еще один простой пример: кнопка может быть видна после загрузки данных и запуска кнопки, чтобы сделать что-то с данными, которые вы извлекли. Опять же, ваш пример хорош, и я не утверждаю, что это не должен быть принятый ответ. При этом ваше полное заявление (даже полное) из yourNameVariable will be be always null when using "wherever you would like throughout your class". не относится ко всем случаям ... (продолжение) – Rbar 18 December 2017 в 19:47
  • 5
    Существует несколько случаев, когда вы можете использовать значение, полученное за пределами выборки данных. Моя точка зрения заключается лишь в том, что существует более чем один способ кошки кошки; некоторые более легкие решения, такие как тот, который я опубликовал, могут работать лучше для некоторых людей, и ваши могут работать лучше для многих других. Не нужно работать, просто обмениваясь знаниями здесь. – Rbar 18 December 2017 в 19:47
Другие вопросы по тегам:

Похожие вопросы: