Search code examples
flutterdartrethinkdbdart-null-safetysqflite

How do I handle this type issue in Flutter?


I'm following a tutorial series that creates a messaging app in flutter. It's slightly outdated but I'm trying to follow along with the most updated version.

I'm getting the following error:

The argument type 'Object?' can't be assigned to the parameter type 'String'.

For this code snippet: (everything after tryParse is underlined in red).

      return chatsWithLatestMessage.map<Chat>((row) {
        final int? unread = int.tryParse(chatsWithUnreadMessages.firstWhere(
          (ele) => row['chat_id'] == ele['chat_id'],
        orElse: () => {'unread': 0})['unread']);

I tried adding .toString() at the end, but then I get more errors because it's supposed to be int:

Use a non-nullable type for a final variable initialized with a non-nullable value.dartunnecessary_nullable_for_final_variable_declarations
A value of type 'String' can't be assigned to a variable of type 'int?'.
Try changing the type of the variable, or casting the right-hand type to 'int?'.dartinvalid_assignment
The argument type 'Object?' can't be assigned to the parameter type 'String'.

I also tried adding .toString() in a few other places in the code, but then got errors saying that what came after .toString() could not be used with Strings.

I think there's something more going on here with all the types. Any ideas what I can do? I'm a total beginner so odds are, the fix is something very basic that I'm just not seeing. I can edit to include more code & files upon request.

This is the whole function that the code snippet lives in:

  @override
  Future<List<Chat>> findAllChats() {
    return _db.transaction((txn) async {
      final chatsWithLatestMessage = await txn.rawQuery(''' SELECT messages.* FROM 
      (SELECT
        chat_id, MAX(created_at) AS created_at
        FROM messages
        GROUP BY chat_id
      ) AS latest_messages
      INNER JOIN messages
      ON messages.chat_id = latest_messages.chat_id
      AND messages.created_at = latest_messages.created_at
      ''');

      final chatsWithUnreadMessages = 
          await txn.rawQuery('''SELECT chat_id, count(*) as unread
      FROM messages
      WHERE receipt = ? 
      GROUP BY chat_id
      ''', ['delivered']);

      return chatsWithLatestMessage.map<Chat>((row) {
        final int? unread = int.tryParse(chatsWithUnreadMessages.firstWhere(
          (ele) => row['chat_id'] == ele['chat_id'],
        orElse: () => {'unread': 0})['unread']).toString();

        final chat = Chat.fromMap(row);
        chat.unread = unread!;
        chat.mostRecent = LocalMessage.fromMap(row);
        return chat;
      }).toList();
    });
  }

Edit: here is the chat model:

import 'package:rethink_chat/models/local_message.dart';

class Chat {
  late String id; // added late
  int unread = 0;
  List<LocalMessage>? messages = [];
  LocalMessage? mostRecent; // added late

  Chat(this.id, {this.messages, this.mostRecent});

  toMap() => {'id': id};
  factory Chat.fromMap(Map<String, dynamic> json) => Chat(json['id']);
}

Solution

  • The first bug, is that you are declaring the value final int? unread... and assign it to value that will never be a null, and it will never be null because you are using the orElse part,
    To solve this, you can do one of the following:
    1- declare your unread value as a final int unread
    2- or remove the orElse part and set your chat.unread to chat.unread = unread ?? 0;

    The second bug, is that you've declared the unread value as an int? but you are assigning it to a string value by writing orElse: () => {'unread': 0})['unread']).toString() the .toString() and this must be removed for either of the solutions above

    ---- Update:

      final chatsWithUnreadMessages = 
          await txn.rawQuery('''SELECT chat_id, count(*) as unread
      FROM messages
      WHERE receipt = ? 
      GROUP BY chat_id
      ''', ['delivered']);
    

    The above code will return for you a List<Map<String, dynamic>>, Ex:

    chatsWithUnreadMessages = [
        {
            "chat_id": 1,
            "unread": 3
        },
        {
            "chat_id": 1,
            "unread": 3
        }
    ]
    

    and after mapping them, the firstWhere will return for you a whole record of the list, so if it found a record that mets row['chat_id'] == ele['chat_id'] it will return the whole row and the whole row will be something like:

    {
        "chat_id": 1,
        "unread": 3
    }
    

    and that is an Object to flutter and it is not String so it cannot be parse-able!

    //Your code
    return chatsWithLatestMessage.map<Chat>((row) {
      final int? unread = int.tryParse(chatsWithUnreadMessages.firstWhere(
        (ele) => row['chat_id'] == ele['chat_id'],
      orElse: () => {'unread': 0})['unread']).toString();
        ....
    
    //Updated code
    return chatsWithLatestMessage.map<Chat>((row) {
      final int? unread = int.tryParse(chatsWithUnreadMessages
          .firstWhere((ele) => row['chat_id'] == ele['chat_id'],
              orElse: () => {'unread': 0})['unread']
          .toString());
      ...
    });
    //What I done is I entered the ".toString()" inside the int.tryParse() parenthesis
    

    I believe that this would solve the problem