0

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

enter image description here

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();

The Employee 123
  • 494
  • 1
  • 14
  • 27

3 Answers3

3

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.

Ewald Benes
  • 622
  • 6
  • 13
  • I am currently trying your solution, but what if I'm trying to get the "Resident's data such as, name, address and etc. I'm only trying to get the "Cities" for just one TextView. I'll comment again after I'm done trying. Thank you for helping. – The Employee 123 Sep 23 '17 at 19:40
  • @TheStudent123 I've edited my answer according to the question – Ewald Benes Sep 23 '17 at 19:48
  • It is stated that "foreach not applicable to type 'com.google.firebase.database.DataSnapshot' ". Sorry for not saying this earlier but I thought I could resolve it. – The Employee 123 Sep 23 '17 at 19:53
  • Sorry forgot the `DataSnapshot.getChildren` method. – Ewald Benes Sep 23 '17 at 19:54
  • Thank you, I am almost there but I am required to add in a semicolon at the `for(DataSnapshot node : dataSnapshot.getChildren()) ` a bubble prompted out stating "; expected". Even after I click "alt + Enter" with `for(DataSnapshot node = (DataSnapshot) dataSnapshot.getChildren();` – The Employee 123 Sep 23 '17 at 20:04
  • The semicolon is at the pure end of my code snippet – Ewald Benes Sep 23 '17 at 20:32
  • Sorry my mistake. But there is still an error that says java.lang.NullPointerException. Give me a minute, I'll update my question to show the codes. – The Employee 123 Sep 23 '17 at 20:44
  • Of course you get a NPE because you need to break your for loop with an if. I'll update. – Ewald Benes Sep 23 '17 at 20:59
  • Thank you for your patience in helping me, I really appreciate it. Have a Nice Day =D – The Employee 123 Sep 24 '17 at 09:04
  • 1
    @TheStudent123 you're welcome! Nevertheless I recommend to take a look at this [video](https://youtu.be/ran_Ylug7AE) about how to structure your data. I think your data model might not be the best fit for what you're trying to accomplish. – Ewald Benes Sep 25 '17 at 08:31
  • Thanks for the video, I'll definitely need to restructure my data tree. – The Employee 123 Sep 25 '17 at 10:19
1

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/

EdgeDev
  • 2,376
  • 2
  • 20
  • 37
  • Alright, I edited it. I read the Firebase Docs, it will now allow the User to Read and Write data within my Database. I changed it from "auth != null". – The Employee 123 Sep 23 '17 at 18:55
  • when you attach a ValueEventListener you get your Data by casting it to your model class and you can't call a reference on it. for example `London london = dataSnapshot.getValue(London.class);` – EdgeDev Sep 23 '17 at 19:06
  • `City city = dataSnapshot.getValue(City.class);` from there you can get all your other nested classes - `city.getPolice(); city.getGuards(); , city.getResident();` – EdgeDev Sep 23 '17 at 19:16
  • I am trying out the solution and other solutions posted. Thank you for helping. I'll comment again once I'm done. – The Employee 123 Sep 23 '17 at 19:40
0

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(); 
                 .....
Cadet
  • 376
  • 1
  • 9
  • I'm currently trying out the solution. But I am currently trying to prompt out the Resident's details and the Cities that they are assigned to. Will the loop still work? Thank you for helping. I'll comment again once I'm done. – The Employee 123 Sep 23 '17 at 19:42
  • To retrieve the Resident's details, I would suggest to keep them in the firebase DB as objects of class (not separated fields). Say, define class TheResident, so you can retrieve it (with regard to the code above) like: TheResident myResident = tds1.getValue(TheResident.class). Of course, insert to the DB should be also done by object of this class. Must to define all required fields in the class, setters/getters and empty constructor. Read more in the documentation. – Cadet Sep 23 '17 at 20:03
  • Thank you for your comment. I will try editing the way I save my data within the Firebase DB. =D – The Employee 123 Sep 24 '17 at 09:03