Android - Firebase - Getting Child of Child Data

2019-06-12 03:30发布

问题:

Aim

To prompt the data of the current user such as name, address, neighbourhood by acquiring them through the Data Tree within Firebase Database

Picture of the Data Tree

Desrciption

The "London" and "Washington" child within the Data Tree are added in by the Admin account, which means that they are not fixed and that an additional child such as "New York" can be added.

Which would mean that the newly edited Data Tree will have under "Users", there will be "London", "Washington", and "New York"

The other childs, such as those shown below are fixed.

  • Guards

    • name
    • neighbourhood
  • Police

    • address
    • name
    • neighbourhood
  • Resident

    • address

    • image

    • name

    • neighbourhood

    • position

    • status

Problem

I receive a "java.lang.NullPointerException" after attempting to prompt the data of the current user who is logged in.

The error is showing at the code String stgUserHomeName = dataSnapshot.child("Users").getValue().toString();.

I was able to acquire my desired data such as "name", "address" and etc. before but when I had a problem when I added in a Not-Fixed parent such as "London" and "Washington".

SettingsActivity Class

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;
import com.squareup.picasso.Callback;
import com.squareup.picasso.NetworkPolicy;
import com.squareup.picasso.Picasso;

import java.io.File;


public class SettingsActivity extends AppCompatActivity {

    private DatabaseReference jSettingsDatabase;
    private FirebaseUser jFirebaseCurrentUser;

    private Toolbar jSettingsToolbar;

    private ImageView jSettingsImageView;
    private TextView jSettingsDisplayName;
    private TextView jSettingsStatus;
    private TextView jSettingsAddress;
    private TextView jSettingsHomeName;

    private Button jSettingsDetailsBtn;
    private Button jSettingsImageBtn;

    private static final int jSettingsGallerySelect = 1;

    private StorageReference jSettingsStorageReference;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        jSettingsStorageReference = FirebaseStorage.getInstance().getReference();

        jFirebaseCurrentUser = FirebaseAuth.getInstance().getCurrentUser();

        String settingsUserID = jFirebaseCurrentUser.getUid();

        jSettingsDatabase = FirebaseDatabase.getInstance().getReference().child("Resident").child(settingsUserID);
        jSettingsDatabase.keepSynced(true);

        jSettingsImageView = (ImageView) findViewById(R.id.settingUserImg);

        jSettingsToolbar = (Toolbar) findViewById(R.id.settingsToolBar);
        setSupportActionBar(jSettingsToolbar);
        getSupportActionBar().setTitle("Settings");
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);


        jSettingsDisplayName = (TextView) findViewById(R.id.settingUserNameTxt);
        jSettingsStatus = (TextView) findViewById(R.id.settingUserStatusTxt);
        jSettingsAddress = (TextView) findViewById(R.id.settingUserAddressTxt);
        jSettingsHomeName = (TextView) findViewById(R.id.settingsUserHomeTxt);

        jSettingsDetailsBtn = (Button) findViewById(R.id.settingChangeDetailsBtn);
        jSettingsImageBtn = (Button) findViewById(R.id.settingChangeImageBtn);

        jSettingsDatabase.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                String stgUserHomeName = dataSnapshot.child("Users").getValue().toString();
                String stgUserName = dataSnapshot.child("Users").child(stgUserHomeName).child("name").getValue().toString();
                String stgUserStatus = dataSnapshot.child("Users").child(stgUserHomeName).child("status").getValue().toString();
                String stgUserHomeAddress = dataSnapshot.child("Users").child(stgUserHomeName).child("address").getValue().toString();
                final String stgUserImage = dataSnapshot.child("Users").child(stgUserHomeName).child("image").getValue().toString();

                jSettingsDisplayName.setText(stgUserName);
                jSettingsStatus.setText(stgUserStatus);
                jSettingsAddress.setText(stgUserHomeAddress);

                if(!stgUserImage.equals("default")){
                    Picasso.with(SettingsActivity.this).load(stgUserImage).networkPolicy(NetworkPolicy.OFFLINE)
                            .placeholder(R.drawable.avataricon).into(jSettingsImageView, new Callback() {
                        @Override
                        public void onSuccess() {

                        }

                        @Override
                        public void onError() {
                            Picasso.with(SettingsActivity.this).load(stgUserImage).placeholder(R.drawable.avataricon).into(jSettingsImageView);
                        }
                    });
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });

        jSettingsDetailsBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String stgUserStatusValue = jSettingsStatus.getText().toString();
                String stgUserAddressValue = jSettingsAddress.getText().toString();
                String stgUserNameValue = jSettingsDisplayName.getText().toString();
                Intent intentDetails = new Intent(SettingsActivity.this, DetailsActivity.class);
                intentDetails.putExtra("stgUserNameValue" , stgUserNameValue);
                intentDetails.putExtra("stgUserStatusValue", stgUserStatusValue);
                intentDetails.putExtra("stgUserAddressValue", stgUserAddressValue);
                startActivity(intentDetails);
            }
        });

        jSettingsImageBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intentGallery = new Intent();
                intentGallery.setType("image/*");
                intentGallery.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(Intent.createChooser(intentGallery, "SELECT IMAGE"), jSettingsGallerySelect);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == jSettingsGallerySelect && resultCode == RESULT_OK){

            Uri imageUri = data.getData();

            String currentUserID = jFirebaseCurrentUser.getUid();

            StorageReference imageFilePath = jSettingsStorageReference.child("profileImages").child(currentUserID+".jpg");

            imageFilePath.putFile(imageUri).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>(){
                @Override
                public void onComplete(@NonNull Task<UploadTask.TaskSnapshot> task) {
                    @SuppressWarnings("VisibleForTests")
                    String downloadImageUrl = task.getResult().getDownloadUrl().toString();

                    jSettingsDatabase.child("image").setValue(downloadImageUrl).addOnCompleteListener(new OnCompleteListener<Void>() {
                        @Override
                        public void onComplete(@NonNull Task<Void> task) {
                            if(task.isSuccessful()){
                                Toast.makeText(SettingsActivity.this, "Upload Successful", Toast.LENGTH_SHORT).show();
                            }else{
                                Toast.makeText(SettingsActivity.this, "Upload Failed", Toast.LENGTH_SHORT).show();
                            }
                        }
                    });
                }
            });
        }
    }
}

Code Explanation

The jSettingsHomeName and stgUserHomeName are meant to be the "London", "Washington", and etc.

Solution to almost Similar Problem

In the link: How to get child of child value from firebase in android? , it shows how a programmer can get the child of a child but the reason as to why I am unable to follow the is because my "London" and "Washington" child tier isn't fixed

Update: Trying Solution given by Ewald B.

jSettingsDatabase.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        for(DataSnapshot node : dataSnapshot.getChildren()) {
            String stgUserHomeName = node.getKey();
            String stgUserName = node.child("Resident").child(settingsUserID).child("name").getValue().toString();
            String stgUserStatus = node.child("Resident").child(settingsUserID).child("status").getValue().toString();
            String stgUserHomeAddress = node.child("Resident").child(settingsUserID).child("address").getValue().toString();
            final String stgUserImage = node.child("Resident").child(settingsUserID).child("image").getValue().toString();

            jSettingsHomeName.setText(stgUserHomeName);
            jSettingsDisplayName.setText(stgUserName);
            jSettingsStatus.setText(stgUserStatus);
            jSettingsAddress.setText(stgUserHomeAddress);

            if(!stgUserImage.equals("default")){
                Picasso.with(SettingsActivity.this).load(stgUserImage).networkPolicy(NetworkPolicy.OFFLINE)
                        .placeholder(R.drawable.avataricon).into(jSettingsImageView, new Callback() {
                    @Override
                    public void onSuccess() {

                    }

                    @Override
                    public void onError() {
                        Picasso.with(SettingsActivity.this).load(stgUserImage).placeholder(R.drawable.avataricon).into(jSettingsImageView);
                    }
                });
            }
        }

    }

    @Override
    public void onCancelled(DatabaseError databaseError) {

    }
});

Error

java.lang.NullPointerException at com.example.task.app.SettingsActivity$1.onDataChange(SettingsActivity.java:91)

Line 91 points to String stgUserName = node.child("Resident").child(settingsUserID).child("name").getValue().toString();

回答1:

According to the data model and looking at this statement

jSettingsDatabase = FirebaseDatabase.getInstance().getReference().child("Resident").child(settingsUserID);

there's no node User underneath /Resident/userId. The query needs to start at the database's root which, I assume, is User.

In order to get London, Washington, etc. you need to adapt the code to:

jSettingsDatabase = FirebaseDatabase.getInstance().getReference().child("Users");
...
jSettingsDatabase.addValueEventListener(new ValueEventListener() {
   @Override
   public void onDataChange(DataSnapshot dataSnapshot) {
      for(DataSnapshot node : dataSnapshot.getChildren()) {
         // you will get all cities
         String stgUserHomeName = node.getKey();
         if(!"Washington".equals(stgUserHomeName)) // or whatever city you need
            continue;
         // add some more conditional logic to cope with the distinct subtrees that don't have the same properties
         // London's Resident has more properties than Washington --> exception is thrown then
         // to get the resident's data
         node.child("Resident").child(userId).child("address")...
         node.child("Resident").child(userId).child("image")...
         // or
         node.child("Resident").child(userId).getValue(Resident.class);
         ...
      }
      ....
   }
});

There's no user ID in your tree that is needed for this query. But it might be necessary depending on the DB's access rules. Obviously the other queries need to be adapted as well. The city names are also not values but a key (must be unique) so what is important to call DataSnapshot.getKey() method.

In your case the whole database from the User downwards will be fetched and on the client all cities that are not needed will be thrown away. That's a waste of resources.



回答2:

for testing purposes only set your Firebase Realtime Database rule to allow anyone read and write

eg

{
  "rules": {
     ".write": "true",
     ".read": "true"
    }
}

Check out Firebase Rules documentation https://firebase.google.com/docs/database/security/



回答3:

As soon as you have multiple children at the node, it's needed to scan them at the loop. The event indicates that "some" child was changed. Also, try to change the event to onChildAdded. Like below:

public void onChildAdded(DataSnapshot dataSnapshot, String s) {
        if (dataSnapshot.getChildrenCount() > 0) {
            for (DataSnapshot ds1 : dataSnapshot.getChildren()) {
                String stgUserHomeName = ds1.getValue.toString(); 
                 .....