How to map a list in Flutter

List in Flutter is a collection of items. It is the most common collection where we keep ordered objects.

A common list looks quite simple.

var list = [1, 2, 3];

However, when a List collects a combination of list and map inside, it might look complicated.

Consider the following list.

var questions = [
    {
      'question': 'Who are you?',
      'answer': [
        'Robot',
        'Human',
        'Alien',
      ],
    },
    {
      'question': 'What is your name?',
      'answer': [
        'Robu',
        'Honu',
        'Alu',
      ],
    },
    {
      'question': 'What do you eat?',
      'answer': [
        'Electricity',
        'Everything',
        'Water of Mars',
      ],
    },
    {
      'question': 'What do you want?',
      'answer': [
        'Follow the instruction',
        'Go to war and destroy.',
        'Go back to Mars.',
      ],
    },
  ];

In the above code, a list contains a map. A map is an object that associates keys and values.

However, both keys and values can be any type of object. Exactly that happens here.

The above list has four maps. Each map again has two key-value pair. The first key-value pair are both String.

But the second key-value pair is String and List<String>.

In our previous discussion, we have two separate lists of items. On the contrary, here, we want to relate question and answer in one single list.

How do you Map a list in flutter?

Now, each question might have several answers. For a test case, we will learn how we can map this list in Flutter.

Let us consider a simple code like the following one.

import 'package:flutter/material.dart';

import 'question.dart';
import 'answer.dart';

main() {
  runApp(const QuizApp());

}

class QuizApp extends StatelessWidget {
  const QuizApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Playxis - Play + Lexis',
      home: QuizPage(),
    );
  }
}

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

  @override
  State<QuizPage> createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  int index = 0;
  void increment() {
    setState(() {
      index = index + 1;
    });
    if (index == questions.length) {
      index = 0;
    }
  }

  var questions = [
    {
      'question': 'Who are you?',
      'answer': [
        'Robot',
        'Human',
        'Alien',
      ],
    },
    {
      'question': 'What is your name?',
      'answer': [
        'Robu',
        'Honu',
        'Alu',
      ],
    },
    {
      'question': 'What do you eat?',
      'answer': [
        'Electricity',
        'Everything',
        'Water of Mars',
      ],
    },
    {
      'question': 'What do you want?',
      'answer': [
        'Follow the instruction',
        'Go to war and destroy.',
        'Go back to Mars.',
      ],
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Playxis - Play + Lexis',
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('${questions[index]['question']}'),
            const SizedBox(
              height: 10.0,
            ),
            Container(
              width: double.infinity,
              margin: const EdgeInsets.all(10.0),
              child: ElevatedButton(
                onPressed: increment,
                child: const Text('Answer 1'),
              ),
            ),
            Container(
              width: double.infinity,
              margin: const EdgeInsets.all(10.0),
              child: ElevatedButton(
                onPressed: increment,
                child: const Text('Answer 2'),
              ),
            ),
            Container(
              width: double.infinity,
              margin: const EdgeInsets.all(10.0),
              child: ElevatedButton(
                onPressed: increment,
                child: const Text('Answer 3'),
              ),
            ),            
          ],
        ),
      ),
    );
  }
}

If we run this code, it looks very simple.

We have hard coded the question and answer. As a result, if we run the App, we see the following output.


Hard coding a List in Flutter
Hard coding a List in Flutter

At the same time, taking a close look at the code tells us to refactor the code. We have unnecessarily repeated ourselves.

Instead, we can extract two separate custom Widgets. One for the Question, and the other for the Answer.

The Question Widget looks like the following.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class Question extends StatelessWidget {  
  const Question({Key? key, required this.questions, required this.index})
      : super(key: key);
  final List<Map<String, Object>> questions;
  final int index;

  @override
  Widget build(BuildContext context) {
    return Text(
      '${questions[index]['question']}',
      style: GoogleFonts.laila(
        textStyle: const TextStyle(
          fontSize: 30.0,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

We will pass two final variables. One is the List of questions which is a List<Map<String, Object>>.

Actually, the data type of List is Map. And again, the Data type of Map is String and List. However, Dart infers the List inside Map as an Object.

After all, everything in Dart is Object.

On the contrary, the Answer Widget needs a final String variable which will display the answer of the List.

And, besides, it also needs a VoidCallback function.

Why?

Because we want to press the ElevatedButton to change the question.

Subsequently, with the change of the question, the answers will also change.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class Answer extends StatelessWidget {
  const Answer({
    Key? key,
    required this.answer,
    required this.pointToOnPress,
  }) : super(key: key);

  final String answer;
  final VoidCallback pointToOnPress;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.all(10.0),
      child: ElevatedButton(
        onPressed: pointToOnPress,
        child: Text(
          answer,
          style: GoogleFonts.langar(
            textStyle: const TextStyle(
              fontSize: 30.0,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }
}

Now we can change the top-level main() function.

We can call the Widgets inside the Column Widgets.

import 'package:flutter/material.dart';

import 'question.dart';
import 'answer.dart';

main() {
  runApp(const QuizApp());

}

class QuizApp extends StatelessWidget {
  const QuizApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Playxis - Play + Lexis',
      home: QuizPage(),
    );
  }
}

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

  @override
  State<QuizPage> createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  int index = 0;
  void increment() {
    setState(() {
      index = index + 1;
    });
    if (index == questions.length) {
      index = 0;
    }
  }

  var questions = [
    {
      'question': 'Who are you?',
      'answer': [
        'Robot',
        'Human',
        'Alien',
      ],
    },
    {
      'question': 'What is your name?',
      'answer': [
        'Robu',
        'Honu',
        'Alu',
      ],
    },
    {
      'question': 'What do you eat?',
      'answer': [
        'Electricity',
        'Everything',
        'Water of Mars',
      ],
    },
    {
      'question': 'What do you want?',
      'answer': [
        'Follow the instruction',
        'Go to war and destroy.',
        'Go back to Mars.',
      ],
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Playxis - Play + Lexis',
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Question(questions: questions, index: index),
            Answer(answer: 'Answer 1', pointToOnPress: increment),
            Answer(answer: 'Answer 2', pointToOnPress: increment),
            Answer(answer: 'Answer 3', pointToOnPress: increment),
          ],
        ),
      ),
    );
  }
}

Now, as we press the Button, each time the question will change.

It happens because the index number changes with the press of the button.

Question(questions: questions, index: index),

Inside the Question Widget we have accessed each question the following way.

'${questions[index]['question']}',

And the above code makes the sense. We can access any List this way.

However, the answers do not change. It remains the same. But we want to access the answers the same way as we have accessed the questions.

To do that, we need to map the list.

How do I get a List of Maps in Dart?

The map method of any list returns one value that we again convert to list.

Especially in Flutter, the Column Widget‘s children property returns a List of Widgets.

Consequently, when we run the code, we see that each question comes with multiple answers associated with it.


Map a List in Flutter first example
Map a List in Flutter first example

However, we need to use the spread operators to add two lists and make them one

Because the Column Widget’s children property returns one List of Widgets. Right?

Therefore, the final code looks like the following.

import 'package:flutter/material.dart';

import 'question.dart';
import 'answer.dart';

main() {
  runApp(const QuizApp());

}

class QuizApp extends StatelessWidget {
  const QuizApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Playxis - Play + Lexis',
      home: QuizPage(),
    );
  }
}

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

  @override
  State<QuizPage> createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  int index = 0;
  void increment() {
    setState(() {
      index = index + 1;
    });
    if (index == questions.length) {
      index = 0;
    }
  }

  var questions = [
    {
      'question': 'Who are you?',
      'answer': [
        'Robot',
        'Human',
        'Alien',
      ],
    },
    {
      'question': 'What is your name?',
      'answer': [
        'Robu',
        'Honu',
        'Alu',
      ],
    },
    {
      'question': 'What do you eat?',
      'answer': [
        'Electricity',
        'Everything',
        'Water of Mars',
      ],
    },
    {
      'question': 'What do you want?',
      'answer': [
        'Follow the instruction',
        'Go to war and destroy.',
        'Go back to Mars.',
      ],
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Playxis - Play + Lexis',
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Question(questions: questions, index: index),
            ...(questions[index]['answer'] as List<String>).map((answer) {
              return Answer(answer: answer, pointToOnPress: increment);
            }).toList(),
          ],
        ),
      ),
    );
  }
}

The Column Widget’s children property now adds two lists in one list. And the questions and answers are not separated anymore.

With the change of one question now we can get the associated answer.


Map a List in Flutter second example
Map a List in Flutter second example

We have solved the hardest part of the Quiz App that we are finally going to build.

In the next section we will build a “Play with Lexis” App that will help us to practice the uncommon vocabulary in English.

However, if you want to clone this preparatory App building process, please clone the related GitHub repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Technical blog

Twitter

Comments

2 responses to “How to map a list in Flutter”

  1. […] we have been building an interesting Quiz App. While building the app, we have learned a few important concepts on theme. We have used a custom […]

  2. […] we have been building an interesting Quiz App. While building the app, we have learned a few important concepts on the […]

Leave a Reply