48 votes

Comment retourner la valeur de DataSnapshot comme résultat d'une méthode ?

Je n'ai pas beaucoup d'expérience avec Java. Je ne sais pas si cette question est stupide, mais j'ai besoin d'obtenir un nom d'utilisateur à partir de la base de données en temps réel Firebase et de retourner ce nom comme résultat de cette méthode. J'ai donc trouvé comment obtenir cette valeur, mais je ne comprends pas comment la renvoyer comme résultat de cette méthode. Quelle est la meilleure façon de procéder ?

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 votes

Comme @PeterHaddad l'a mentionné, la méthode onDataChange est asynchrone, ce qui signifie qu'elle renverra toujours null. Puis-je demander : pourquoi avez-vous besoin de retourner le nom de l'utilisateur ? Pourquoi ne pas simplement utiliser ce nom dans la méthode onDataChange ?

2 votes

Comme l'a dit @PeterHaddad, parce que onDataChange() est appelée asynchrone, cela ne fonctionnera pas. Veuillez consulter ma réponse.

1 votes

Lisez ceci pour comprendre pourquoi les API de Firebase sont asynchrones : medium.com/@CodingDoug/

105voto

Alex Mamo Points 44420

C'est un problème classique des API Web asynchrones. Vous ne pouvez pas renvoyer maintenant quelque chose qui n'a pas encore été chargé. En d'autres termes, vous ne pouvez pas simplement créer une variable globale et l'utiliser en dehors de l'API. onDataChange() car il sera toujours null . Cela se produit parce que onDataChange() est dite asynchrone. Selon la vitesse de votre connexion et l'état, il peut s'écouler de quelques centaines de millisecondes à quelques secondes avant que ces données soient disponibles.

Mais non seulement Firebase Realtime Database charge les données de manière asynchrone, mais presque toutes les API Web modernes le font aussi, car cela peut prendre un certain temps. Ainsi, au lieu d'attendre les données (ce qui peut conduire à des dialogues d'application non réactifs pour vos utilisateurs), le code de votre application principale continue pendant que les données sont chargées sur un thread secondaire. Ensuite, lorsque les données sont disponibles, votre méthode onDataChange() est appelée et peut utiliser les données. En d'autres termes, au moment où onDataChange() est appelée, vos données ne sont pas encore chargées.

Prenons un exemple, en plaçant quelques déclarations de journal dans le code, pour voir plus clairement ce qui se passe.

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!");
}

Si nous exécutons ce code, la sortie sera :

Avant d'attacher l'écouteur !

Après avoir attaché l'écouteur !

Dans la méthode onDataChange() !

Ce n'est probablement pas ce que vous attendiez, mais cela explique précisément pourquoi vos données sont null lors de son retour.

La première réaction de la plupart des développeurs est d'essayer de "réparer" ce problème. asynchronous behavior Je vous le déconseille personnellement. Le web est asynchrone, et plus vite vous l'accepterez, plus vite vous pourrez apprendre à devenir productif avec les API web modernes.

J'ai trouvé qu'il était plus facile de recadrer les problèmes pour ce paradigme asynchrone. Au lieu de dire "D'abord obtenir les données, puis les enregistrer", je formule le problème comme suit : "Commencer à obtenir les données. Lorsque les données sont chargées, enregistrez-les". Cela signifie que tout code qui requiert les données doit se trouver à l'intérieur de onDataChange() ou appelé depuis l'intérieur, comme ceci :

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

Si vous voulez l'utiliser en dehors, il existe une autre approche. Vous devez créer votre propre callback pour attendre que Firebase vous renvoie les données. Pour ce faire, vous devez tout d'abord créer un objet interface comme ça :

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

Ensuite, vous devez créer une méthode qui récupère réellement les données de la base de données. Cette méthode doit ressembler à ceci :

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

À la fin, il suffit d'appeler readData() et passe une instance de la méthode MyCallback comme argument lorsque vous en avez besoin comme ceci :

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

C'est la seule façon d'utiliser cette valeur en dehors de l'Union européenne. onDataChange() méthode. Pour plus d'informations, vous pouvez également consulter le site suivant vidéo .

Editar: 26 février 2021

Pour plus d'informations, vous pouvez consulter l'article suivant :

Et la vidéo suivante :

0 votes

Cela peut-il être réalisé en utilisant l'interface Future et Callable ? developer.Android.com/reference/java/util/concurrent/Future

0 votes

Salut Peter ! Cela pourrait fonctionner. Je n'ai pas encore essayé mais je le ferai. Je ne sais pas si vous le savez, mais il y a un site intéressant à consulter. article .

0 votes

Oui, je l'ai vu avant puisqu'il est écrit comme un commentaire à plusieurs endroits. Mais je ne l'ai pas lu, car je sais comment fonctionne l'asynchrone après avoir lu quelques questions-réponses ici. Quoi qu'il en soit, je l'ai lu maintenant et je pense qu'il est préférable de ne pas utiliser Future puisque nous devons utiliser newSingleThreadExecutor() cela crée un autre thread, et puisque l'api est asychrone, elle est déjà dans un thread en java et nous devons maintenir ce thread aussi et écrire plus de code.

6voto

Alex Mamo Points 44420

En partant du "19.6.0" le SDK Firebase Realtime Database contient une nouvelle méthode appelée get(), qui peut être appelée soit sur un objet DatabaseReference, soit sur un objet Query :

Ajout de DatabaseReference#get() et Query#get(), qui renvoient les données du serveur même si des données plus anciennes sont disponibles dans le cache local.

Comme nous le savons déjà, l'API Firebase est asynchrone. Nous devons donc créer un callback pour cela. D'abord, créons une interface :

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

Et une méthode qui prend comme argument un objet de type 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());
            }
        }
    });
}

Maintenant, pour lire les données, vous devez simplement appeler la méthode ci-dessus en passant comme argument un objet de type FirebaseCallback :

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

Pour plus d'informations, vous pouvez consulter l'article suivant :

Et la vidéo suivante :

2voto

Voici une idée folle, à l'intérieur de onDataChange, mettez-le à l'intérieur d'un TextView avec la visibilité disparue. textview.setVisiblity(Gone) ou autre chose, puis faire quelque chose comme

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

puis plus tard l'obtenir avec textview.getText().toString()

juste une idée simple et folle.

0 votes

Et comment savoir quand il faut récupérer le texte du TextView (la méthode sera asynchrone).

0 votes

À moins que le textview a déjà été détruit. Alors c'est juste une mauvaise idée.

2voto

Rbar Points 977

Je crois que je comprends ce que vous demandez. Bien que vous disiez vouloir "renvoyer" la valeur (en soi) à partir de la méthode de récupération, il suffit de dire que vous voulez en fait pouvoir utiliser la valeur récupérée une fois la récupération terminée. Dans ce cas, voici ce que vous devez faire :

  1. Créez une variable au début de votre classe
  2. Récupérer votre valeur (ce que vous avez fait en grande partie correctement)
  3. Définir la variable publique dans votre classe égale à la valeur récupérée

Une fois la récupération réussie, vous pouvez faire beaucoup de choses avec la variable. 4a et 4b sont des exemples simples :

4a. Editar: A titre d'exemple d'utilisation, vous pouvez déclencher tout ce dont vous avez besoin pour être exécuté dans votre classe qui utilise yourNameVariable (et vous pouvez être sûr qu'il yourNameVariable non nul)

4b. Editar: À titre d'exemple d'utilisation, vous pouvez utiliser la variable dans une fonction qui est déclenchée par le onClickListener d'un bouton.


Essayez ça.

// 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);
  }
}

Ensuite, vous pouvez utiliser yourNameVariable où vous le souhaitez dans votre classe.


Note : assurez-vous de vérifier que yourNameVariable n'est pas nulle lorsqu'elle est utilisée car onDataChange est asynchrone et peut ne pas être terminée au moment où vous tentez de l'utiliser ailleurs.

2 votes

C'est une réponse incorrecte. Ce n'est pas ainsi que les choses se passent en ce qui concerne les méthodes asynchrones. En utilisant cette réponse, vous aurez votre variable yourNameVariable pour être toujours null lorsque vous utilisez "où vous le souhaitez dans votre classe". Vous pouvez l'utiliser uniquement dans votre onDataChange() méthode. C'est tout.

0 votes

@AlexMamo, votre solution de définir une interface fonctionne bien, mais la partie suivante de votre commentaire est incorrecte : " yourNameVariable pour être toujours null ." Pour ceux qui recherchent une solution plus légère (qui ne nécessite pas de définir une interface), cela fonctionne bien. Une fois que la variable a été récupérée et définie, ils peuvent déclencher toutes les fonctions qui ont besoin du résultat et savoir que la variable n'est pas null (tant qu'il existe sur firebase en premier lieu). J'ai mis à jour mon exemple pour que vous puissiez voir un cas d'utilisation concret.

0 votes

En outre, l'appel de cette méthode n'a rien à voir avec le comportement asynchrone des méthodes. Appeler cette méthode est la même chose que d'utiliser cette ligne Log.d(TAG, "hi there, " + yourNameVariable); à l'intérieur de onDataChange() méthode. Cela signifie que lorsque onDataChange() est déclenchée, vous appelez cette méthode.

1voto

Md. Asaduzzaman Points 13121

Utilisez LiveData comme type de retour et observer les changements de sa valeur pour exécuter l'opération désirée.

private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();

public MutableLiveData<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?
            String userName = dataSnapshot.getValue(String.class);
            userNameMutableLiveData.setValue(userName);
        }

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

    return userNameMutableLiveData;
}

Alors de votre Activity/Fragment observez le LiveData et à l'intérieur onChanged effectuer l'opération souhaitée.

getUserName().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String userName) {
        //here, do whatever you want on `userName`
    }
});

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X