web-dev-qa-db-ja.com

メソッドの結果としてDataSnapshot値を返す方法は?

Javaの経験はあまりありません。この質問が愚かかどうかはわかりませんが、Firebaseリアルタイムデータベースからユーザー名を取得し、このメソッドの結果としてこの名前を返す必要があります。そのため、この値を取得する方法を見つけましたが、このメソッドの結果として値を返す方法がわかりません。これを行う最良の方法は何ですか?

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) {}
    });
}
30
Ilya Cucumber

これは、非同期Web APIの古典的な問題です。まだロードされていないものを返すことはできません。つまり、グローバル変数を作成して、onDataChange()メソッドの外で使用することはできません。これは常にnullになるためです。これは、onDataChange()メソッドが非同期で呼び出されるために発生しています。接続速度と状態によっては、データが利用可能になるまでに数百ミリ秒から数秒かかる場合があります。

ただし、Firebase Realtime Databaseがデータを非同期的にロードするだけでなく、他の最新のWeb 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!");
}

このコードを実行すると、出力は次のようになります。

リスナーを接続する前に!

リスナーを接続した後!

OnDataChange()メソッド内!

これはおそらく予想したものとは異なりますが、データを返すときにnullである理由を正確に説明しています。

ほとんどの開発者にとっての最初の対応は、このasynchronous behaviorを試して「修正」することです。 Webは非同期であり、それを受け入れるとすぐに、最新のWeb 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()メソッドの外でその値を使用できる唯一の方法です。詳細については、こちらもご覧くださいビデオ

67
Alex Mamo

これは、onDataChange内で、textview.setVisiblity(Gone)または何かが表示されなくなった状態でTextView内に配置してから、次のようなことを行うクレイジーなアイデアです。

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

その後、textview.getText().toString()で取得します

クレイジーでシンプルなアイデア。

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は非同期であり、他の場所で使用しようとしたときに完了していない可能性があるため、onDataChangeがnullでないことを確認してください。 。

2
Rbar

これは機能しています。値をパラメーターとして別の関数に渡すことで、onDataChangeの外部のデータスナップショットから値を使用できます.

// Get Your Value
private void getValue() {

    fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {

        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

            String yourValue = (String) dataSnapshot.getValue();
            useValue(yourValue);

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

}

// Use Your Value
private void useValue(String yourValue) {

    Log.d(TAG, "countryNameCode: " + yourValue);

}

私とRbarの答えの主な違いは、値をパラメーターとして関数に渡し、それを使用していることです。

0
DragonFire