Search code examples
flutterfirebasegoogle-cloud-firestorecrud

Firebase cloud firestore CRUD operations not performing


I am making a flutter app using firebase. I have stored my database in firebase cloud firestore. It's a simple medicine reminder app, where the user can add medicines and set reminder for upcoming doses and the app reminds the user to take medicines by sending a notification at the time picked by the user. The data added to the database is not reflected in the app.However, the data is being added in the database, as shown in the firebase console. However, the app homescreen shows the "nothing to add" even if there's data in database. Also the document ID is different from the user ID. I want the document ID to be the same as user UID that's generated during authentication. So that each user's data is mapped with their ID. The document structure in firestore is : One main collection called "Medicines" This collection will have different documents with an ID which will be ther user's user ID. Corresponding to each of these documents, there's be a subcollection called "User-Medicines" which will have all the medicines of that particular user in the form of different documents.

The mediicnes I am adding are all being added in the "Medicines" collection, instead they should be stored in the subcollection "User-Medicines". Similarly for every app user, there should be a seperate "User-Medicines" sub-collection where only their mediicnes will be stored.

Here's the CRUD operations :

final FirebaseFirestore _firestore =
    FirebaseFirestore.instance; //initializing firebase firestore
final CollectionReference _mainCollection = _firestore.collection(
    'Medicines'); //defining the main collection where all the database information will be stored

class Database {
  //this class contains all the crud functions
  static String? userID;

  //Create
  static Future<void> addMedicine({
    required String medicineName,
    required String dosageAmount,
    required int? frequency,
    required List<TimeOfDay?> doseTime,
    required String instructions,
    required String additionalInformation,
  }) async {
    DocumentReference documentReferencer =
        _mainCollection.doc(userID).collection('User-Medicines').doc();

    //below line converts the dosetime to firestore compatible format
    List<Map<String, int>> timesForFirestore =
        MedicineDataConverter.timeOfDayListToMapList(doseTime);

    Map<String, dynamic> medicine = {
      "Medicine Name": medicineName,
      "Dosage Amount": dosageAmount,
      "Frequency": frequency,
      "Date and Time": timesForFirestore,
      "Instructions": instructions,
      "Additional Information": additionalInformation,
    };

    await documentReferencer
        .set(medicine)
        .whenComplete(() => print('Medicine added to the database'))
        .catchError((e) => print(e));
  }

  //Read
  static Stream<QuerySnapshot> readMedicine() {
    return _mainCollection.doc(userID).collection('User-Medicines').snapshots();
    // CollectionReference medicineCollection =
    //     _mainCollection.doc(userID).collection('User-Medicines');

    // return medicineCollection.snapshots();
  }

  //Update
  static Future<void> updateMedicine({
    required String medicineName,
    required String dosageAmount,
    required int? frequency,
    required List<TimeOfDay?> doseTime,
    required String instructions,
    required String additionalInformation,
    required String docID,
  }) async {
    DocumentReference documentReferencer =
        _mainCollection.doc(userID).collection('Medicines').doc(docID);

    List<Map<String, int>> timesForFirestore =
        MedicineDataConverter.timeOfDayListToMapList(doseTime);

    Map<String, dynamic> medicine = {
      "Medicine Name": medicineName,
      "Dosage Amount": dosageAmount,
      "Frequency": frequency,
      "Date and Time": timesForFirestore,
      "Instructions": instructions,
      "Additional Information": additionalInformation,
    };

    await documentReferencer.update(medicine);
    // .whenComplete(() => print('Data updated'))
    // .catchError((e) => print(e));
  }

  //Delete
  static Future<void> deleteMedicine({
    required String docID,
  }) async {
    DocumentReference documentReferencer =
        _mainCollection.doc(userID).collection('Medicines').doc(docID);

    await documentReferencer.delete();
    // .whenComplete(() => print('Data deleted'))
    // .catchError((e) => print((e)));
  }
}

And here's the home screen code:

body: StreamBuilder<QuerySnapshot>(
        stream: Database.readMedicine(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }

          if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
            return const Center(
              child: Text('Nothing to show. Add some medicines!'),
            );
          }

          return ListView.builder(
            itemCount: snapshot.data!.docs.length,
            itemBuilder: (context, index) {
              DocumentSnapshot medicine = snapshot.data!.docs[index];

              return Padding(
                padding: const EdgeInsets.all(15.0),
                child: ListTile(
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(20)),
                  selectedColor: Colors.deepPurple,
                  iconColor: Colors.white,
                  textColor: Colors.white,
                  splashColor: Colors.purple,
                  title: Text(medicine['medicineName']),
                  subtitle: Text(medicine['dosageAmount']),
                  trailing: IconButton(
                    onPressed: () {
                      String docID = medicine.id;
                      deleteConfirmationDialogue(context, docID);
                    },
                    icon: const Icon(Icons.delete),
                  ),
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => MedicineDetailEditScreen(
                            medicineDocument: medicine),
                      ),
                    );
                  },
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
        splashColor: Colors.purple,
        onPressed: () {
          //go to addmedicine page
          Navigator.of(context).pushNamed(addMedicineRoute);
        },
        child: const Icon(Icons.add),
      ),

Solution

  • This question has two sub-questions:

    1. The data not being displayed on screen even after being added in the database.
    2. The data not being arranged in the desired manner in the database, i.e. document id for each document in Medicine collection is same as user's uid, that's generated during authentication, and each user's data being stored in a separate User-Mediicne sub-collection.

    I fixed the 2. issue. In the Database class, even tough I am accessing the current user's uid, that's not being passed to the addMedicine function thus the userID is auto-generated by firebase and as a result we've a new document for each medicine in Medicine collection and that has a sub-collection User-Medicine and then finally the data is getting stored in the document in User-Medicine.

    This was fixed by adding a parameter userID in the addMedicine function inside the Database class. So the modified addMedicine function will be like this :

    static String? get userID => 
    FirebaseAuth.instance.currentUser?.uid;//getting current user's uid
    static Future<void> addMedicine({
    required String medicineName,
    required String dosageAmount,
    required int? frequency,
    required List<TimeOfDay?> doseTime,
    required String instructions,
    required String additionalInformation,
    required String? userID, //including userID
    }) async {
    //nothing changes here
    }
    

    And I accessed the current user's id from the Database class and passed it in the addMedicine function whenever I called it. Like this :

    late final String? userId = Database.userID;
    Database.addMedicine(
                      medicineName: medicineNameController.text,
                      dosageAmount: dosageAmountController.text,
                      frequency: frequency,
                      doseTime: doseTimes,
                      instructions: instructionsController.text,
                      additionalInformation:
                          additionalInformationController.text,
                      userID: userId,
                    );