Home > OS >  An exception was throw by _MapStream<QuerySnapshot<Map<String, dynamic>>, List<Use
An exception was throw by _MapStream<QuerySnapshot<Map<String, dynamic>>, List<Use

Time:06-24

I am trying to list some data from a firestore database. While pulling the data I got this exception:

======== Exception caught by provider ==============================================================
The following assertion was thrown:
An exception was throw by _MapStream<QuerySnapshot<Map<String, dynamic>>, List<UserModel>?> listened by

StreamProvider<List<UserModel>?>, but no `catchError` was provided.

Exception:
type 'int' is not a subtype of type 'double?'

====================================================================================================

These are my database functions:

  // users list from snapshot
  List<UserModel>? _usersListFromSnapshot(QuerySnapshot snapshot) {
    return snapshot.docs.map((doc) {
      return UserModel(
        uid: doc.get('uid'),
        username: doc.get('username') ?? '',
        email: doc.get('email') ?? '',
        type: doc.get('type') ?? '',
        balance: doc.get('balance'),
        usertag: doc.get('usertag') ?? '',
        connected: doc.get('connected') ?? '',
      );
    }).toList();
  }

  // get users stream
  Stream<List<UserModel>?> get usersList {
    User? user = _auth.currentUser;
    UserModel? userModel = _userFromFirebaseUser(user);
    final CollectionReference userRef = FirebaseFirestore.instance.collection("users");
    final List? connectedList = userModel?.connected;
    final Query inList = userRef.where('uid', whereIn: connectedList);
    return inList.snapshots().map(_usersListFromSnapshot);
  }

This is my user.dart:

class UserModel {
  String? uid;
  String? username;
  String? email;
  String? type;
  double? balance;
  String? usertag;
  List<dynamic>? connected;

  UserModel({this.uid, this.username, this.email, this.type, this.balance, this.usertag, this.connected});

  // receive data from the server
  factory UserModel.fromMap(map) {
    return UserModel(
      uid: map['uid'],
      username: map['username'],
      email: map['email'],
      type: map['type'],
      balance: map['balance'],
      usertag: map['usertag'],
      connected: map['connected']
    );
  }

  // send data to the server
  Map<String, dynamic> toMap() {
    return {
      'uid': uid,
      'username': username,
      'email': email,
      'type': type,
      'balance': balance,
      'usertag': usertag,
      'connected': connected,
    };
  }
}

This is my Listbuilder:

class ConnectedScreen extends StatefulWidget {
  const ConnectedScreen({Key? key}) : super(key: key);

  @override
  _ConnectedScreenState createState() => _ConnectedScreenState();
}

class _ConnectedScreenState extends State<ConnectedScreen> {

  User? user = FirebaseAuth.instance.currentUser;
  UserModel userModel = UserModel();

  @override
  void initState() {
    super.initState();
    FirebaseFirestore.instance
        .collection('users')
        .doc(user!.uid)
        .get()
        .then((value) {
      userModel = UserModel.fromMap(value.data());
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: customAppBar("Connect Users", "back", ""),
      body: Padding(
        padding: EdgeInsets.all(8.0),
        child: SingleChildScrollView(
          child: StreamProvider<List<UserModel>?>.value(
            value: AuthService().usersList,
            initialData: [],
            child: ConnectedListBuilder(),
          ),
        ),
      ),
    );
  }
}


class ConnectedListBuilder extends StatefulWidget {
  const ConnectedListBuilder({Key? key}) : super(key: key);

  @override
  _ConnectedListBuilderState createState() => _ConnectedListBuilderState();
}

class _ConnectedListBuilderState extends State<ConnectedListBuilder> {
  @override
  Widget build(BuildContext context) {

    final connectedUsers = Provider.of<List<UserModel>?>(context);

    if (connectedUsers!.isNotEmpty) {
      return Padding(
        padding: EdgeInsets.only(top: 8.0),
        child: ListView.builder(
          itemCount: connectedUsers.length,
          itemBuilder: (context, index) {
            return ConnectedUserTile(userModel: connectedUsers[index]);
          },
        ),
      );
    } else if (connectedUsers.isEmpty) {
      return Text("List is empty");
    }
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}


class ConnectedUserTile extends StatelessWidget {

  final UserModel? userModel;

  const ConnectedUserTile({Key? key, required this.userModel}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(8.0),
      child: Card(
        margin: EdgeInsets.fromLTRB(20, 6, 20, 0),
        child: ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage('https://picsum.photos/100'),
            radius: 25,
          ),
          title: Text(
            "${userModel!.username}",
            style: TextStyle(
              color: Colors.black54,
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          subtitle: Text(
            "${userModel!.usertag}",
            style: TextStyle(
              color: Colors.black38,
              fontSize: 14,
            ),
          ),
        ),
      ),
    );
  }
}

What's interesting is that this exact code works for another ListView, so I don't think it's an error with the database functions, but I´m out of ideas how to try fixing it. I also didn't find much helpful information on the internet about this, so I would be very thankful for any help.

CodePudding user response:

Looks like the issue is in parsing the balance field. In the instances that it works, the balance was probably formatted as a double, e.g. "1.23" and so when it's converted to a map, it's converted as a double, which is a subtype of double?.

When it's not working, it's most likely dealing with an integer balance, like "25" - The value stored in the map will then have a type of int, which is not a subtype of double?, because int is not a subtype of double.

To fix this, you can simply ensure that you're always dealing with a double, by calling toDouble() on the value.

// receive data from the server
factory UserModel.fromMap(map) {
  return UserModel(
    uid: map['uid'],
    username: map['username'],
    email: map['email'],
    type: map['type'],
    balance: map['balance'].toDouble(), // <- Change this line
    usertag: map['usertag'],
    connected: map['connected']
  );
}
  • Related