2

I'm using Firebase to populate my RecyclerView, but the problem is that every time I open the app, I need to swipe down to refresh and then the list of items is available.

Here is my code

public class MainActivity extends AppCompatActivity {


private static final int RC_PHOTO_PICKER =  2;

DatabaseReference db;
FirebaseHelper helper;
MyAdapter adapter;
RecyclerView rv;
EditText nameEditTxt,grpTxt,descTxt,linkTxt;
FirebaseAuth.AuthStateListener authListener;
SwipeRefreshLayout swipeRefresh;
Uri downloadUrl;
String Admin_code;
FirebaseStorage mfirebaseStorage;
private StorageReference mEventPhotoReference;
FloatingActionButton fab;
SharedPreferences sharedPreferences;
ProgressBar spinner;


static boolean calledAlready=false;
public MyAdapter adapter1;


@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    spinner = (ProgressBar)findViewById(R.id.progressBar);
    spinner.setVisibility(View.GONE);


    mfirebaseStorage=FirebaseStorage.getInstance();

    if(!calledAlready){
        FirebaseDatabase.getInstance().setPersistenceEnabled(true);
        calledAlready=true;
    }

    swipeRefresh=(SwipeRefreshLayout)findViewById(R.id.swiperefresh);

    //SETUP RECYCLER
    rv = (RecyclerView) findViewById(R.id.rv);
    rv.setLayoutManager(new LinearLayoutManager(this));



    //INITIALIZE FIREBASE DB
    db= FirebaseDatabase.getInstance().getReference();
    mEventPhotoReference=mfirebaseStorage.getReference().child("Event Photos");
    helper=new FirebaseHelper(db);



    //ADAPTER
    adapter=new MyAdapter(this,helper.retrieve());
    rv.setAdapter(adapter);
    adapter.notifyDataSetChanged();




    swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            rv.setAdapter(adapter);
            swipeRefresh.setRefreshing(false);
        }
    });

    sharedPreferences= PreferenceManager.getDefaultSharedPreferences(this);
    Admin_code=sharedPreferences.getString(getString(R.string.Admin_code),getString(R.string.Admin_default_value));

    Log.e("MainActivity","" + Admin_code);

    fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            displayInputDialog();
        }
    });

    fab.setVisibility(View.GONE);
    showBtn();

}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
protected void onResume() {
    showBtn();
    super.onResume();

}



@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void showBtn(){
    Admin_code=sharedPreferences.getString(getString(R.string.Admin_code),getString(R.string.Admin_default_value));
    if(Objects.equals(Admin_code, "28011996")){

        fab.setVisibility(View.VISIBLE);

    }
    else
        fab.setVisibility(View.GONE);


}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_main, menu);
    return true;
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_logout) {
        FirebaseAuth.getInstance().signOut();
        startActivity(new Intent(MainActivity.this, LoginActivity.class));
        return true;
    }
    if(id==R.id.settings){
        Intent settingsIntent=new Intent(this,Settings.class);
        startActivity(settingsIntent);
        return true;
    }

    return super.onOptionsItemSelected(item);
}


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if ((requestCode& 0xffff) == RC_PHOTO_PICKER && resultCode == RESULT_OK) {
        Uri selectedImageUri = data.getData();


        StorageReference photoRef=mEventPhotoReference.child(selectedImageUri.getLastPathSegment());


        photoRef.putFile(selectedImageUri)
                .addOnSuccessListener(this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
                    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                        // When the image has successfully uploaded, we get its download URL
                        downloadUrl = taskSnapshot.getDownloadUrl();
                        Toast.makeText(MainActivity.this,"Photo selected successfully",Toast.LENGTH_LONG).show();
                    }

                }).addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Toast.makeText(MainActivity.this,"there was a problem uploading photo",Toast.LENGTH_LONG).show();
            }
        });

    }


}

//DISPLAY INPUT DIALOG
private void displayInputDialog()
{
    Dialog d=new Dialog(this);
    d.setTitle("Save To Firebase");
    d.setContentView(R.layout.input_dialog);

    nameEditTxt= (EditText) d.findViewById(R.id.nameEditText);
    grpTxt= (EditText) d.findViewById(R.id.propellantEditText);
    descTxt= (EditText) d.findViewById(R.id.descEditText);
    Button saveBtn= (Button) d.findViewById(R.id.saveBtn);
    Button photoBtn=(Button)d.findViewById(R.id.photoBtn);
    linkTxt = (EditText) d.findViewById(R.id.linkEditText);



    photoBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
            intent.setType("image/jpeg");
            intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
            startActivityForResult(Intent.createChooser(intent, "Complete action using"), RC_PHOTO_PICKER);

        }
    });


    //SAVE
    saveBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            //GET DATA
            String name=nameEditTxt.getText().toString();
            String propellant=grpTxt.getText().toString();
            String desc=descTxt.getText().toString();
            String link=linkTxt.getText().toString();
            Long tsLong = System.currentTimeMillis()/1000;
            String ts = tsLong.toString();


            //SET DATA
            Spacecraft s=new Spacecraft();


                s.setName(name);
                s.setPropellant(propellant);
                s.setDescription(desc);
                s.setLink(link);
                s.setImageUrl(downloadUrl.toString());
                s.setTimestamp(ts);

            //SIMPLE VALIDATION
            if(name != null && name.length()>0)
            {
                //THEN SAVE
                if(helper.save(s))
                {
                    //IF SAVED CLEAR EDITXT
                    nameEditTxt.setText("");
                    grpTxt.setText("");
                    descTxt.setText("");
                    linkTxt.setText("");
                    downloadUrl=null;



                    adapter=new MyAdapter(MainActivity.this,helper.retrieve());
                    rv.setAdapter(adapter);
                    adapter.notifyDataSetChanged();

                }
            }else
            {
                Toast.makeText(MainActivity.this, "Name Must Not Be Empty", Toast.LENGTH_SHORT).show();
            }

        }
    });

    d.show();
}

After Searching for a while, I found out that I should use notifyDataSetChanged(); but it doesn't work.

Here is my helper class where I'm fetching data:

public class FirebaseHelper {

    DatabaseReference db;
    Boolean saved=null;
    ArrayList<Spacecraft> spacecrafts=new ArrayList<>();

    public FirebaseHelper(DatabaseReference db) {
        this.db = db;
    }

    //WRITE IF NOT NULL
    public Boolean save(Spacecraft spacecraft)
    {
        if(spacecraft==null)
        {
            saved=false;
        }else
        {
            try
            {
                db.child("Spacecraft").push().setValue(spacecraft);
                saved=true;


            }catch (DatabaseException e)
            {
                e.printStackTrace();
                saved=false;
            }
        }

        return saved;
    }

    //IMPLEMENT FETCH DATA AND FILL ARRAYLIST
    private void fetchData(DataSnapshot dataSnapshot)
    {
        spacecrafts.clear();

        for (DataSnapshot ds : dataSnapshot.getChildren())
        {
            Spacecraft spacecraft=ds.getValue(Spacecraft.class);
            spacecrafts.add(spacecraft);

        }

    }

    //READ THEN RETURN ARRAYLIST
    public ArrayList<Spacecraft> retrieve() {
        db.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                fetchData(dataSnapshot);


            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                fetchData(dataSnapshot);

            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                fetchData(dataSnapshot);

            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });
        return spacecrafts;
    }

It'll be great if someone could help me out here. This is the last problem in my project.

AL.
  • 36,815
  • 10
  • 142
  • 281

1 Answers1

1

The problem is due to the adapter not knowing about the data that has been fetched asynchronously. Let's try to look at your code step-by-step:

adapter = new MyAdapter(this, helper.retrieve());

helper.retrieve() will initiate an asynchronous request to fetch data from firebase and immediately returns an empty ArrayList (as the data from firebase hasn't reached the app yet).

rv.setAdapter(adapter);

This line sets the adapter to recyclerView, which has an empty arraylist as data, so it won't show anything on the UI.

After the data is received from firebase, ArrayList<Spacecraft> spacecrafts is updated with new data. But the adapter is not notified about this new data. Ideally, adapter.notifyDataSetChanged() should be called after new data is put into the array list.

Hence, when you swipe to refresh after the initial call, the adapter is set to recycler view, leading to an internal call to notifyDataSetChanged(), which in turn loads the new data on to the UI.

Easiest solution is to notify the adapter when new data is received from Firebase. Below is the pseudo code for it -

  • Create an interface OnFirebaseDataChanged and add a method void dataChanged(); to it.

  • MainAcitivty should implement this interface and the implementation of that method should call adapter.notifyDataSetChanged()

  • helper = new FirebaseHelper(db); should be changed to helper = new FirebaseHelper(db, this);

  • FirebaseHelper's constructor should have a 2nd parameter of type OnFirebaseDataChanged and should save it in a field called dataChangeListener.

  • Add a line at the end of fetchData method in FirebaseHelper dataChangeListener.dataChanged();

The ideal solution would be to architect the code differently, but that topic is out of scope of this question.

Tushar Nallan
  • 784
  • 6
  • 16