-1

I want to get my contacts from my phone and save them into firebase. The following code works if all I wanted to do is save the contact name, mainly because the name cannot be empty in the phone but a problem arises when the nested value contact.phones is null or empty (shown below as "//works if I remove this").

Currently, if the phone field is empty, it throws a "StateError (Bad state: No element)" error.

Also, note that contacts.phone (result.phones after the attempt to remove the elements) is a list so I need to grab the first one.

I have tried to remove those elements from the list but that also sufferers from the same problem, the code to remove the empty phone fields fails for the same reason on this line.

if (!["", null, false, 0].contains(contact.phones?.first))

What is the correct way to remove elements from a list where the nested element is null or empty?



import '../../backend/backend.dart';
import '../../flutter_flow/flutter_flow_theme.dart';
import '../../flutter_flow/flutter_flow_util.dart';
import 'index.dart'; // Imports other custom actions
import 'package:flutter/material.dart';

import 'package:contacts_service/contacts_service.dart';

Future syncContactstoFirebase(String? userID) async {
  List<Contact> contacts = await ContactsService.getContacts();
List result = [];


for (var contact in contacts) {
  if (!["", null, false, 0].contains(contact.phones?.first)) {
    result.add(contact);
  }
}
  final instance = FirebaseFirestore.instance;

  CollectionReference collection =
      instance.collection('users').doc(userID).collection('contacts');

  late Map<String, dynamic> data;
  if (result != null)
    data = {
      'contacts': contacts
          .map((k) => {
                'name ': k.displayName,
                  'phone': k.phones?.first.value //works if I remove this
                    .toString()
                    .replaceAll(new RegExp(r"\s\b|\b\s"), "")
                    .replaceAll(new RegExp(r'[^\w\s]+'), '')
              })
          .toList(),
    };

  return collection
      .doc()
      .set(data)
      .then((value) => print("Contacts Updated"))
      .catchError((error) => print("Failed to update Contacts: $error"));
}

EDIT: See note below.

Is there a way to handle more than one nested element so for instance the mapping code becomes:-

  if (result != null)
    data = {
      'contacts': contacts
          .map((k) => {
                'name ': k.displayName,
                  'phone': k.phones?.first.value, 
                  'email': k.email?.value ///////ADDED THIS////////
                    .toString()
                    .replaceAll(new RegExp(r"\s\b|\b\s"), "")
                    .replaceAll(new RegExp(r'[^\w\s]+'), '')
              })
          .toList(),
    };

user2818170
  • 107
  • 1
  • 10
  • An answer has been found to this question as asked, see my note, but it has exposed a slightly more complex issue for me. There are other fields in the contact list one of them being contacts.email. This can also be supplied with that field or empty. How do I remove elements where both are null or empty, but still add the phone number if the contact.email is null or empty and vice versa? – user2818170 Nov 02 '22 at 04:16
  • so if I'm not mistaken you want to remove the contacts that don't have email and phone number (when both of them together are either null or empty)? – Kimiya Zargari Nov 03 '22 at 05:58
  • if so, that's just a matter of the condition that you pass to your removeWhere function. I added that to the answer I provided for you. Please mark the answer as accepted if your issue is resolved. thank you – Kimiya Zargari Nov 03 '22 at 06:08

3 Answers3

0

I'm not entirely sure, but I believe you would want to replace it with

if (!["", null, false, 0].contains(contact.phones?.firstOrNull))

To use firstOrNull you need

import 'package:collection/collection.dart';
Ivo
  • 18,659
  • 2
  • 23
  • 35
0

When you want to remove a specific group of elements from a list that satisfy a condition, you can use the removeWhere function on your list.

So, in your example, if you want to remove all contacts with null or empty phones from your contacts list, the bellow line of code should do the trick:

 contacts.removeWhere(
    (element) => element.phones == null || element.phones!.isEmpty);

if you want to remove the contacts that don't have phone number and email you can change the condition of the removeWhere like this:

contacts.removeWhere(
    (element) => (element.phones == null || element.phones!.isEmpty) && (element.emails == null || element.emails!.isEmpty));

You can basically add any condition and remove the elements that satisfy that condition

Kimiya Zargari
  • 324
  • 1
  • 14
  • Yes indeed this works and it is the correct answer to my question. Thank you. However, something then became apparent to me once I had it working, In order to make this code more universally useful to me and others facing this issue, I have made the question a little wider Please see the edit. – user2818170 Nov 02 '22 at 04:07
  • @user2818170 is there an edit to the question description or just the title? because I couldn't find the difference in the description. I edited my answer to make it more general and helpful to others with the similar issue. – Kimiya Zargari Nov 02 '22 at 12:05
  • I chose to put it in the comment instead, and then was unable to edit the comment where I referred to an EDIT. So forgive me, but the comment is the extension of the question. – user2818170 Nov 02 '22 at 18:52
  • I am pretty sure this will fail to collect valid phone numbers if the email is blank and vice versa. Somehow if the email is valid, and the phone is not, we only want to remove the nested blank phone element, not the whole element. In short, we want to retain as much usable data as we can. – user2818170 Nov 03 '22 at 14:27
  • @user2818170 did you test it?The code I provided only removes contacts that don't have any phone number or email; if one exists the condition will not be satisfied. – Kimiya Zargari Nov 04 '22 at 13:41
  • I will test, but I predict it will throw away a valid email when you don't have a phone (and vice versa) number, and that is not what is required. We want to keep all valid data. But let me confirm. – user2818170 Nov 05 '22 at 16:49
  • @user2818170 there is an & between the conditions for email and password, it won't delete a contact unless both (due to the presence of &) are null or empty. Either way the answer to your question is the removeWhere() function. check both both are null or empty and delete, as simple as that. – Kimiya Zargari Nov 05 '22 at 17:20
  • since phones doesn't have a phones field, contacts.phones.removeWhere( (element) => (element.phones == null || element.phones!.isEmpty) would cause syntax error. please test my code before dismissing it. – Kimiya Zargari Nov 09 '22 at 05:28
  • Tested it. It crashes just like it did in the question, as I said it would. Your second snippet:- Crashes because if the phone is not null, and the email is, it allows through a null email value. Back to square one. If you replace && with || (not suggested, but is worth a try) it does not crash - it will remove the entire contact if one or other is null, but then removes a valid email when the phone is missing, and vice versa which is also not what the question now asks. The first snippet also allows null values to pass through for anything in the contact list other than phones. – user2818170 Nov 09 '22 at 23:27
0

So simple when I got the syntax right, particularly the use of ! and ? in null safety. An if clause, before it adds the element in the mapping, is what does the trick.

  if (contacts != null)
    data = {
      'contacts': contacts
          .map((k) => {
                 if (k.displayName!.isNotEmpty && k.displayName !=null) 'name': k.displayName,
                 if (k.emails!.isNotEmpty && k.emails != null) 'email': k.emails?.first.value,
                 if (k.phones!.isNotEmpty && k.phones != null) 'phone': k.phones?.first.value
                    .toString()
                    .replaceAll(new RegExp(r"\s\b|\b\s"), "")
                    .replaceAll(new RegExp(r'[^\w\s]+'), '')
                    .replaceAll(new RegExp(r'/^(?!\s*$).+/'), '')
              })
          .toList(),
    };

user2818170
  • 107
  • 1
  • 10