0

I'm currently trying to build an app using Firebase Realtime Database, I already have some data there, which are structured like this (excerpt of the exported JSON)

{
"userdata" : {
    "593QfAvxxxxxxxxxxxHqCMA3" : {
      "calcentries" : {
        "-LJy_hvvm3ySUqM6sQcf" : {
          "candyCount" : 0,
          "evolutionLine" : {
            "candyNeeded" : 25,
            "evolvedSpecies" : 2,
            "precondition" : {
              "buddyKm" : 0,
              "preconditionType" : "NONE"
            }
          },
          "evolvedSpeciesTransferCount" : 0,
          "newPokedexEntry" : false,
          "pokemonCount" : 0,
          "possibleEvoCountWithPreconditionsMet" : 0,
          "possibleEvolutionCount" : 0,
          "preconditionsMet" : false,
          "sourceSpecies" : 1,
          "sourceSpeciesCountAfterEvolutions" : 0,
          "sourceSpeciesTransferCount" : 0
        },
        "-LJy_hwMVTM20f0c5JOJ" : { ...

}

I try to display them in a RecyclerView by using a FirebaseRecyclerAdapter (Firebase UI 4.1.0). This is how I initialize the RecyclerView and the adapter in the onCreate of my activity

mRecyclerView = findViewById(R.id.rv_calculation_input);

RecyclerView.LayoutManager layoutManager = new GridLayoutManager(this,1, LinearLayoutManager.VERTICAL,false);

mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);

FirebaseRecyclerOptions<CalculationInputEntry> opt
     = new FirebaseRecyclerOptions.Builder<CalculationInputEntry>()
         .setLifecycleOwner(this)
         .setQuery(AppUtil.getCurrentUserRef().child("calcentries"), CalculationInputEntry.class)
         .build();

mAdapter = new FirebaseRecyclerAdapter<CalculationInputEntry, CalculationInputEntryViewHolder>(opt) {        
     @NonNull
     @Override
     public CalculationInputEntryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
          View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main, parent, false);
          return new CalculationInputEntryViewHolder(v);
     }

     @Override
     protected void onBindViewHolder(@NonNull CalculationInputEntryViewHolder holder, int position, @NonNull CalculationInputEntry model) {
           holder.bindEntry(model);
     }
 };

mRecyclerView.setAdapter(mAdapter);

The view remains empty... By setting the lifecycle owner, manually calling startListening and stopListening on the adapter should not be necessary, right?

I set the Firebase log level to debug and found quite some entries that "CHILD_ADDED" events are raised, even showing the expected data...

D/EventRaiser: Raising /userdata/593QxxxxxxxxxCMA3/calcentries: CHILD_ADDED: { -LJy_tNakAl19tH5H_tb: {preconditionsMet=false, sourceSpeciesCountAfterEvolutions=0, newPokedexEntry=false, possibleEvoCountWithPreconditionsMet=0, possibleEvolutionCount=0, candyCount=0, sourceSpeciesTransferCount=0, evolutionLine={candyNeeded=25, precondition={buddyKm=0, preconditionType=NONE}, evolvedSpecies=375}, pokemonCount=0, sourceSpecies=374, evolvedSpeciesTransferCount=0} }

but they seem to never get through to the UI.

Here are the involved model classes, I'm using Lombok to generate getters, setters and the needed constructors (at least I hope I included everything needed). I already changed some fields from Enums to Strings, just in case this could be a problem... Caution: lots of code ahead...

CalculationInputEntry

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CalculationInputEntry implements Comparable, Parcelable {

    //Pokédex number for less data redundancy when serializing
    private int sourceSpecies;
    private SpeciesEvolution evolutionLine;

    //input variables
    private int candyCount;
    private int pokemonCount;
    private boolean newPokedexEntry;
    private boolean preconditionsMet;

    //calculation and result variables
    private int possibleEvolutionCount; // 0 when preconditionsMet false
    private int sourceSpeciesTransferCount;
    private int evolvedSpeciesTransferCount;
    private int sourceSpeciesCountAfterEvolutions;
    private int possibleEvoCountWithPreconditionsMet;

    public CalculationInputEntry(int species, SpeciesEvolution evolution, int candyCount, int pokemonCount) {
        this.sourceSpecies = species;
        this.evolutionLine = evolution;
        this.candyCount = candyCount;
        this.pokemonCount = pokemonCount;
        this.preconditionsMet = false;
        this.newPokedexEntry = false;
    }

   //some other methods, including compareTo and the Parcelable stuff
}

SpeciesEvolution

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class SpeciesEvolution implements Parcelable {
    private int candyNeeded;
    private EvolutionPrecondition precondition;
    private int evolvedSpecies;

    //Parcelable stuff here
}

EvolutionPrecondition

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class EvolutionPrecondition implements Parcelable {

    private String preconditionType;
    private int buddyKm;
    private String evoItem;

   //Parcelable stuff
}

Is there anything wrong with my models or am I just too blind to see the real problem? Any hint would be highly appreciated...

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Kerstin
  • 19
  • 5

2 Answers2

1

By setting the lifecycle owner, manually calling startListening and stopListening on the adapter should not be necessary, right?

No, both are necessary. In order to be able to display data from the Firebase database you need to start listening for changes and for that you should add the following line of code in the onStart() method:

@Override
protected void onStart() {
    super.onStart();
    mAdapter.startListening();
}

To stop listening foir changes you need add the following line of code in the onStop() method like this:

@Override
protected void onStop() {
    super.onStop();
    if(mAdapter != null) {
        mAdapter.stopListening();
    }
}

Please see my answer from this post where I have explained why you should remove the listener.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thanks. I might have misunderstood this https://github.com/firebase/FirebaseUI-Android/blob/master/database/README.md#automatic-listening but it really sounded to me like handing over the Activity as lifecycle owner would suffice. BUT... even when I start/stop listening manually, like you showed in your answer, nothing changes, the RecyclerView still remains empty. – Kerstin Aug 16 '18 at 19:11
  • Have you tried to log in your adapter class the values that you get from the database, to see if you are getting them correctly? – Alex Mamo Aug 16 '18 at 19:23
  • I guess that's my main problem... The overridden methods (onCreate-/onBindViewHolder) are never called, not even once. That probably means that the data never arrive (although I get those EventRaised log messages). This again makes me think that the models could be the problem... – Kerstin Aug 16 '18 at 20:13
  • Are you sure you have the correct getters for your fields in your model class? Are you also sure that `AppUtil.getCurrentUserRef().child("calcentries")` point the the exact node where the children are only `CalculationInputEntry` objects? – Alex Mamo Aug 17 '18 at 09:05
1

I solved it. Neither the start/stopListening is needed, nor are my getters/setters or the DatabaseReference wrong...

It's this line

mRecyclerView.setHasFixedSize(true);

after I had removed it, I could revert everything to the state shown in the question. I got the idea from this GitHub issue https://github.com/firebase/FirebaseUI-Android/issues/204 Thanks to Alex Mamo anyways :)

Kerstin
  • 19
  • 5