Search code examples
androidfirebasegoogle-cloud-firestorefirebaseui

Firestore query returns correct total number of values, but not the actual values


I'm trying to populate a recycler view with data fetched from Firestore, following a few tutorials and this documentation: https://github.com/firebase/FirebaseUI-Android/tree/master/firestore#using-the-firestorerecycleradapter. It seems, that the number fetched from the database (currently 3) is correct, but there is no data displayed. In addition I already have log messages throughout the code to monitor the values, but the actual values remain empty.

Screenshot showing reyclerview items, but no data

Here is what I do:

In my main fragment I create an adapter:
private var adapter: FirestoreCommanderNamesRecyclerAdapter? = null

Then start listening to the the adapter:

    override fun onStart() {
        super.onStart()
        adapter!!.startListening()
        Log.d(TAG, "onStart -> start listening to the adapter")
    }

Then in the onViewCreated I call a function, that should populate the recyclerview:

   private fun invokeRecyclerView(){
        val rootRef = FirebaseFirestore.getInstance()
        val query = rootRef!!.collection("commanders")
        //TODO: Here is something off
        val options = FirestoreRecyclerOptions.Builder<FirestoreCommanderNames>().setQuery(query, FirestoreCommanderNames::class.java).build()

        adapter = FirestoreCommanderNamesRecyclerAdapter(options)
        binding.fragmentCommanderSettingsRecyclerview.layoutManager = LinearLayoutManager(this.context)
        binding.fragmentCommanderSettingsRecyclerview.adapter = adapter

    }

Here is the class FirestoreCommanderNamesRecyclerAdapter:

    class FirestoreCommanderNamesRecyclerAdapter constructor(options: FirestoreRecyclerOptions<FirestoreCommanderNames>):
        FirestoreRecyclerAdapter<FirestoreCommanderNames, FirestoreCommanderNamesViewHolder>(options){
    
        private val TAG = "CmdrNamesRecyclerAdapte"
    
        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): FirestoreCommanderNamesViewHolder {
            Log.d(TAG, "onCreateViewHolder started")
            val view = LayoutInflater.from(parent.context).inflate(R.layout.settings_name_list_item, parent, false)
            return FirestoreCommanderNamesViewHolder(view)
        }
    
        override fun onBindViewHolder(
            holder: FirestoreCommanderNamesViewHolder,
            position: Int,
            model: FirestoreCommanderNames
        ) {
            Log.d(TAG, "onBindViewHolder started")
            Log.d(TAG, "onBindViewHolder Model Commander Name: ${model.commander_names_ID}, position: $position")
            holder.setCommanderName(model.commander_names_ID )
        }
    
        override fun onError(e: FirebaseFirestoreException) {
            super.onError(e)
            Log.d(TAG, "Error: $e")
        }
    
    }

And the FirestoreCommanderNamesViewHolder:

    class FirestoreCommanderNamesViewHolder constructor(private val view: View) : RecyclerView.ViewHolder(view){
    
        private val TAG = "CmdrNamesViewHolder"
    
        internal fun setCommanderName(commanderNames: String){
            Log.d(TAG, "Viewholder Value: $commanderNames")
            val textView = view.findViewById<TextView>(R.id.settings_list_item_textview_name)
            textView.text = commanderNames
        }
    }

the data class FirestoreCommanderNames used:

    data class FirestoreCommanderNames(
    
        val commander_names_ID: String = "",
        val commander_names_NAME: String = ""
    
    )

This is, how the data is structured: Data structure used

And my dependencies used:

    // Import the Firebase BoM
    implementation platform('com.google.firebase:firebase-bom:32.0.0')

    // When using the BoM, don't specify versions in Firebase dependencies
    implementation 'com.google.firebase:firebase-analytics-ktx'
    implementation("com.google.firebase:firebase-crashlytics-ktx")
    implementation("com.google.firebase:firebase-auth-ktx")
    implementation("com.google.firebase:firebase-firestore-ktx")
    implementation 'com.google.firebase:firebase-core:21.1.1'
    implementation("com.google.firebase:firebase-perf-ktx")
    implementation("com.google.firebase:firebase-config-ktx")
    implementation("com.google.firebase:firebase-messaging-ktx")
    implementation 'com.google.firebase:firebase-database-ktx'
    implementation 'com.google.firebase:firebase-storage-ktx'

    // Firebase UI
    implementation 'com.firebaseui:firebase-ui-auth:8.0.2'
    implementation 'com.firebaseui:firebase-ui-database:8.0.2'
    implementation 'com.firebaseui:firebase-ui-firestore:8.0.2'
    implementation 'com.firebaseui:firebase-ui-storage:8.0.2'

    implementation "androidx.legacy:legacy-support-v4:1.0.0"
    implementation "androidx.recyclerview:recyclerview:1.3.0"
</pre>

What I already checked

Dependencies, User authentication (user is logged in), Firestore rules (I know, this is not safe, but for testing it should do the trick): rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if true; } } }

Putting a dummy text value in the setup (rather than the query), Query itself, Resource references

Question

Any idea, why the data is properly retrieved?

Solution

  • As Doug commented, the structure of your FirestoreCommanderNames doesn't match the data in the document you show, so the Firestore SDK has no way to automatically map the values for you.

    The most direct mapping is:

    data class FirestoreCommanderNames(
        val Name: String = ""
    )
    

    Note how the Name here matches the exact field name in the database? That's how Firebase knows how to map between the database and your class.

    If you want to also map the document ID from the metadata to your class, you can use a @DocumentId annotation to do so:

    data class FirestoreCommanderNames(
        @DocumentId val ID: String = "",
        val Name: String = ""
    )