0

I make two requests to the Firebase database, and then load the data into the XML. But it doesn't load them and just leaves the default, I suspect it's because I'm not handling asynchronous requests properly in the fragment that Im working (GoogleMap.InfoWindowAdapter). Can anybody help me?

public class MyInfoWindowAdapter implements GoogleMap.InfoWindowAdapter  {

    Context context;
    static String user, descripcion, username, foto;
    DatabaseReference databaseReference, usersReference;
    TextView title_mod,snipp_mod;
    ImageView imagen_mod;

    public MyInfoWindowAdapter(Context context) {
        this.context = context;
    }

        @Override
        public View getInfoWindow(@NonNull Marker marker) {
            if (!marker.getTitle().toString().equals("Este es mi perfil")) {
                View infoView = LayoutInflater.from(context).inflate(R.layout.custom_info, null);
                title_mod = infoView.findViewById(R.id.title_mod);
                snipp_mod = infoView.findViewById(R.id.snipp_mod);
                imagen_mod = infoView.findViewById(R.id.imagen_mod);

                LatLng posicionMarker = marker.getPosition();
                String idLocation = String.valueOf(posicionMarker.latitude).replace(".", "") + "_" + String.valueOf(posicionMarker.longitude).replace(".", "");

                databaseReference = FirebaseDatabase.getInstance().getReference("chismesinfo");
                databaseReference.child(idLocation).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<DataSnapshot> task) {
                        if (task.isSuccessful()) {
                            DataSnapshot snapshot = task.getResult();
                            descripcion = snapshot.child("descripcion").getValue(String.class);
                            user = snapshot.child("user").getValue(String.class);

                            usersReference = FirebaseDatabase.getInstance().getReference("users");
                            usersReference.child(user).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
                                @Override
                                public void onComplete(@NonNull Task<DataSnapshot> task) {
                                    if (task.isSuccessful()) {
                                        DataSnapshot snapshot1 = task.getResult();
                                        username = snapshot1.child("name").getValue(String.class);
                                        if (snapshot1.hasChild("profile")) {
                                            foto = snapshot1.child("profile").getValue(String.class);
                                        } else {
                                            foto = null;
                                        }

                                    }
                                }
                            });
                        }
                    }
                });

                byte[] imageAsByte = Base64.decode(foto.getBytes(), Base64.DEFAULT);
                Bitmap bitmap = BitmapFactory.decodeByteArray(imageAsByte, 0, imageAsByte.length);
                imagen_mod.setImageBitmap(bitmap);

                snipp_mod.setText(descripcion);
                return infoView;
            }
}
}

I was expecting the data to be loaded directly to the interface, but instead I just get the text by default. However, the second time I trigger the event, it does load the data.

1 Answer 1

0

Data is loaded from Firebase (and most modern cloud APIs) asynchronously, as it may take time before it's available. While the data is being loaded, your main code continues - to ensure the user can continue to use the app. Then when the data is loaded, your onComplete is called with that data.

What this means in practice is that your Base64.decode(foto.getBytes()... gets called before foto = snapshot1.child("profile").getValue(String.class); ever runs. If you add some logging or set breakpoints and run in the debugger, you can most easily see this.


Let's add some logging to your code and see the result:

databaseReference = FirebaseDatabase.getInstance().getReference("chismesinfo");
Log.i("Firebase", "Before calling addOnCompleteListener");
databaseReference.child(idLocation).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        Log.i("Firebase", "Inside onComplete");
    }
});
Log.i("Firebase", "After calling addOnCompleteListener");

When you run the above code, you'll get this output:

Before calling addOnCompleteListener
After calling addOnCompleteListener
Inside onComplete

This is probably not the output you expected, but it explains perfectly why your code doesn't work: by the time your Base64.decode(foto.getBytes() runs, the foto variable doesn't have any value yet.


To avoid this problem, any code that needs the data from the database has to be inside the onComplete, be called from there, or be otherwise synchronized. For examples of how to do this (and much more information), see:

This also means that you can't return a view based on the data from the database from the getInfoWindow function; by the time the `` runs, the return infoView; runs the data from the database hasn't been loaded yet. If the caller must get the window, you'll need to pass in a custom callback, similar to the OnCompleteListener that you pass to Firebase. The first link in the list above has a good example of that too.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.