Флаттер/Дротик: вложенное отображение не дает ожидаемого результата

#flutter #dart #mapping

Вопрос:

У меня есть список, который в настоящее время имеет следующий формат:

 final List<Map<String, List<Object>>> _userWords = [
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Dog',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Hund',
          article: 'der',
          plural: 'Hunde',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    },
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Cat',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Katze',
          article: 'die',
          plural: 'Katzen',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    } //map
  ];
 

В настоящее время я пытаюсь отобразить для каждого слова (внешняя карта) значение на каждом языке на отдельной карточке.

 WordList(this.words);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 300,
      child: SingleChildScrollView(
        child: Column(
          //iterating through the outer list, creating one card for each word
          //wrds represents each outer map (key=DateTime)
          children: words.map((wrds) {
            return Card(
              child: Container(
                margin: EdgeInsets.symmetric(vertical: 0, horizontal: 0),
                child: Column(
                  //iterating through each language thats associated with the key
                  //creating a card for each meaning in a different language
                  children: (wrds[wrds.keys.first]).map((lng) {
                    return Card(
                      child: Text(
                        lng.meaning,
                      ),
                    );
                  }).toList(),
                ),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
 

Now this throws the error
type 'List<dynamic>' is not a subtype of type 'List<'Widget>'
I tried to remove the attribute from lng.meaning to just lng and then I get the error
type 'English' is not a subtype of type 'String'
So it appears that the indexing works correctly.
I tried to be specific with the inner toList() function, changing it to ‘.toList()’
which gives the error

 NoSuchMethodError: Class'MappedListIterable<Object,dynamic>'
has no instance method 'toList' with matching arguments.
 

If I just use the outer map and hard code the indexing for each language thats associated to the key I also get the intended result, but I want to create this dynamically. Can anyone explain me what is going on here, and what I am doing wrong?

The way I understand the error is that the result of my mapping is not a list of Widgets. But the way I understand my code is, that I should get a list of Containers. One for each language in the map. Which is what I get with the outer mapping. (changing container to another card does not change the error)

Edit: I had different parts of the app in different files. I restructured it to provide a minimum runnable example to the following

 void main() {
  runApp(MyApp());
}

class English {
  final _properties = [
    {'articles': 'the'},
    //SubjectVerbObject
    {'structure': 'svo'},
  ];
  static String plural = 's';

  final String meaning;
  final DateTime date;
  final String id;
  final String type;

  English(
      {required this.meaning,
      required this.date,
      required this.id,
      required this.type});
}

class German {
  final String meaning;
  final String article;
  final String plural;
  final DateTime date;
  final String id;
  final String type;

  German({
    required this.meaning,
    required this.article,
    required this.plural,
    required this.date,
    required this.id,
    required this.type,
  });
}


class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'VocTrainer',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final List<Map<String, List<Object>>> _userWords = [
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Dog',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Hund',
          article: 'der',
          plural: 'Hunde',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    },
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Cat',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Katze',
          article: 'die',
          plural: 'Katzen',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    } //map
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('VocTrainer')),
      body: SingleChildScrollView(
        child: Column(children: <Widget>[
          Container(
            height: 300,
            child: SingleChildScrollView(
              child: Column(
                children: _userWords.map((wrds) {
                  return Card(
                    child: Container(
                      margin: EdgeInsets.symmetric(vertical: 0, horizontal: 0),
                      decoration: BoxDecoration(
                        border: Border.all(
                          color: Colors.amber.shade800,
                          width: 5,
                        ),
                      ),
                      padding: EdgeInsets.all(10),
                      child: Column(
                        children: (wrds[wrds.keys.first]!).map((lng) {
                          Row(children: [
                            Text(
                              lng.runtimeType.toString()   ': ',
                              style: TextStyle(
                                fontWeight: FontWeight.w300,
                                fontSize: 17,
                                color: Colors.amber.shade900,
                              ),
                            ),
                            Text(
                              lng.meaning,
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                fontSize: 17,
                                color: Colors.amber.shade900,
                              ),
                            ),
                          ]);
                        }).toList(),
                      ),
                    ),
                  );
                }).toList(),
              ),
            ),
          ),
        ]),
      ),
    );
  }
}
 

Интересно, что я уже получаю ошибку здесь, в коде, в котором говорится, что Null списка не может быть присвоен виджету списка.

Однако, если я изменю строку на жестко закодированную

                       child: Column(children: [
                        Row(children: [
                          Text(
                            wrds[wrds.keys.first]![0].runtimeType.toString()  
                                ': ',
                            style: TextStyle(
                              fontWeight: FontWeight.w300,
                              fontSize: 17,
                              color: Colors.amber.shade900,
                            ),
                          ),
                          Text(
                            wrds[wrds.keys.first]![0].toString(),
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 17,
                              color: Colors.amber.shade900,
                            ),
                          ),
                        ]),
                        Row(children: [
                          Text(
                            wrds[wrds.keys.first]![1].runtimeType.toString()  
                                ': ',
                            style: TextStyle(
                              fontWeight: FontWeight.w300,
                              fontSize: 17,
                              color: Colors.amber.shade900,
                            ),
                          ),
                          Text(
                            wrds[wrds.keys.first]![1].toString(),
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 17,
                              color: Colors.amber.shade900,
                            ),
                          ),
                        ]),
 

Это работает. Интересно, что здесь я не могу вызвать wrds[wrds.keys.first]![1].значение из-за ошибки

  wrds[wrds.keys.first]![1].toString()
 

Хотя это работало без каких-либо проблем, когда мой код был разделен на несколько файлов.

Ответ №1:

Я попытался воспроизвести проблему, построив минимальный пример. Поскольку вы не показали , как вы объявили свои классы English и German , я сделал предположение и добавил абстрактный класс Word , который они расширяют.

Кажется, все работает, и я не смог воспроизвести вашу проблему. Однако мне пришлось изменить тип карты на List<Map<String, List<Word>>> вместо List<Map<String, List<Object>>> . С учетом этих изменений код отображает две карты (собаки и кошки), содержащие оба перевода в качестве внутренних карт.

 import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

abstract class Word {
  final String meaning;
  final DateTime date;
  String? article;
  String? plural;
  final String type;
  final String id;

  Word(
    this.meaning,
    this.date,
    this.type,
    this.id,
    this.article,
    this.plural,
  );
}

class German extends Word {
  German({
    required String meaning,
    required DateTime date,
    required String type,
    required String id,
    String? article,
    String? plural,
  }) : super(meaning, date, type, id, article, plural);
}

class English extends Word {
  English({
    required String meaning,
    required DateTime date,
    required String type,
    required String id,
    String? article,
    String? plural,
  }) : super(meaning, date, type, id, article, plural);
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  final List<Map<String, List<Word>>> _userWords = [
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Dog',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Hund',
          article: 'der',
          plural: 'Hunde',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    },
    {
      DateTime.now().toString(): [
        English(
          meaning: 'Cat',
          date: DateTime.now(),
          type: 'Noun',
          id: DateTime.now().toString(),
        ),
        German(
          meaning: 'Katze',
          article: 'die',
          plural: 'Katzen',
          date: DateTime.now(),
          id: DateTime.now().toString(),
          type: 'Noun',
        ),
      ],
    } //map
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          height: 300,
          child: SingleChildScrollView(
            child: Column(
              //iterating through the outer list, creating one card for each word
              //wrds represents each outer map (key=DateTime)
              children: _userWords.map((wrds) {
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: SizedBox(
                      width: MediaQuery.of(context).size.width / 2,
                      child: Container(
                        margin:
                            EdgeInsets.symmetric(vertical: 0, horizontal: 0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: (wrds[wrds.keys.first]!).map((lng) {
                            return Card(
                              child: Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Text(
                                  lng.meaning,
                                ),
                              ),
                            );
                          }).toList(),
                        ),
                      ),
                    ),
                  ),
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
}
 

Обновить

После того, как был приведен пример выполнения, я смог обнаружить ошибку. Сопоставление слов создает экземпляры виджета строки, но не return создает их.

Это должно сработать для отображения:

 children: (wrds[wrds.keys.first]!).map((lng) {
  **return** Row(children: [
    Text(
      lng.runtimeType.toString()   ': ',
      style: TextStyle(
        fontWeight: FontWeight.w300,
        fontSize: 17,
        color: Colors.amber.shade900,
      ),
    ),
    Text(
      lng.meaning,
      style: TextStyle(
        fontWeight: FontWeight.bold,
        fontSize: 17,
        color: Colors.amber.shade900,
      ),
    ),
  ]);
}).toList(),
 

Тем не менее, я все же предлагаю ввести общий интерфейс для слов, чтобы избежать проверок типа и приведения типов.

Комментарии:

1. Это действительно странно. Ваш код работает безупречно и для меня. Я не определил классы таким же образом, но попробовал использовать ваши определения классов, и я получаю ту же ошибку, так что это не может быть так. Я действительно озадачен этим. Когда я просто копирую ваш код, он работает, когда я пытаюсь настроить свой код так, чтобы он выглядел так же, как ваш (у меня он разделен на несколько файлов). Я просто получаю ошибку: тип аргумента «Список<Null>» не может быть присвоен типу параметра » Список<Null><Виджет>». непосредственно в моем коде, хотя я скопировал ваш список.

2. Если вы не возражаете, не стесняйтесь поделиться проектом со мной, и я посмотрю.

3. Это действительно было бы очень полезно. Я уже много раз перепробовал, так что все в полном беспорядке. Завтра я приведу его в более разумное состояние. Я также отредактировал свой оригинальный пост с выполняемым примером (ну, на этот раз он уже показывает ошибку в коде), так что, надеюсь, это облегчит устранение неполадок. Большое спасибо за ваши усилия.

4. Я понял это на вашем примере. Я обновил свой ответ.