image

07 Apr 2026

9K

35K

Building an Interactive Quiz Widget with Timer and Score Tracking in Flutter

Interactive quizzes are an engaging way to test knowledge, provide feedback, and enhance user experience in mobile applications. Flutter, with its expressive UI and robust state management capabilities, is an excellent framework for developing such features. This article will guide you through the process of building a dynamic quiz widget in Flutter, complete with a countdown timer and a comprehensive score tracking system.

Key Components of an Interactive Quiz

  • Question Presentation: Displaying questions and multiple-choice options clearly.
  • User Interaction: Allowing users to select answers and providing immediate feedback.
  • Timer Mechanism: Implementing a countdown timer for each question or the entire quiz.
  • Score Tracking: Accumulating points based on correct answers.
  • State Management: Efficiently managing the quiz's current question, timer, score, and overall flow.
  • Result Display: Presenting the final score and an option to retake the quiz.

Project Setup and Data Model

First, ensure you have a basic Flutter project set up. We'll start by defining the data structure for our questions and their options.

Create a question.dart file (or similar) to define your Question and Option models:


// lib/models/question.dart

class Question {
  final String questionText;
  final List<Option> options;
  final int timeLimitSeconds; // Optional: time limit per question

  Question({
    required this.questionText,
    required this.options,
    this.timeLimitSeconds = 15, // Default time per question
  });
}

class Option {
  final String text;
  final bool isCorrect;

  Option({required this.text, required this.isCorrect});
}

Next, let's create some dummy data to populate our quiz:


// lib/data/quiz_data.dart

import '../models/question.dart';

final List<Question> quizQuestions = [
  Question(
    questionText: 'What is the capital of France?',
    options: [
      Option(text: 'Berlin', isCorrect: false),
      Option(text: 'Madrid', isCorrect: false),
      Option(text: 'Paris', isCorrect: true),
      Option(text: 'Rome', isCorrect: false),
    ],
  ),
  Question(
    questionText: 'Which planet is known as the Red Planet?',
    options: [
      Option(text: 'Earth', isCorrect: false),
      Option(text: 'Mars', isCorrect: true),
      Option(text: 'Jupiter', isCorrect: false),
      Option(text: 'Venus', isCorrect: false),
    ],
  ),
  Question(
    questionText: 'What is 2 + 2?',
    options: [
      Option(text: '3', isCorrect: false),
      Option(text: '4', isCorrect: true),
      Option(text: '5', isCorrect: false),
      Option(text: '6', isCorrect: false),
    ],
  ),
];

Building the Quiz Widget (Stateful Widget)

The core of our quiz will be a StatefulWidget to manage the various states: current question index, score, and timer.


// lib/widgets/quiz_widget.dart

import 'dart:async';
import 'package:flutter/material.dart';
import '../models/question.dart';
import '../data/quiz_data.dart';

class QuizWidget extends StatefulWidget {
  @override
  _QuizWidgetState createState() => _QuizWidgetState();
}

class _QuizWidgetState extends State<QuizWidget> {
  int _currentQuestionIndex = 0;
  int _score = 0;
  bool _quizFinished = false;
  Timer? _timer;
  int _secondsRemaining = 0;
  List<Question> _questions = [];

  @override
  void initState() {
    super.initState();
    _questions = quizQuestions; // Load questions
    _startQuiz();
  }

  void _startQuiz() {
    _currentQuestionIndex = 0;
    _score = 0;
    _quizFinished = false;
    _loadQuestionTimer();
  }

  void _loadQuestionTimer() {
    if (_currentQuestionIndex < _questions.length) {
      _timer?.cancel(); // Cancel any existing timer
      setState(() {
        _secondsRemaining = _questions[_currentQuestionIndex].timeLimitSeconds;
      });
      _timer = Timer.periodic(Duration(seconds: 1), (timer) {
        if (_secondsRemaining > 0) {
          setState(() {
            _secondsRemaining--;
          });
        } else {
          timer.cancel();
          _goToNextQuestion(); // Time's up, move to next question
        }
      });
    } else {
      _timer?.cancel();
      setState(() {
        _quizFinished = true;
      });
    }
  }

  void _answerQuestion(Option selectedOption) {
    if (_quizFinished) return; // Prevent answering after quiz is done or time is up

    _timer?.cancel(); // Stop timer immediately after answering

    if (selectedOption.isCorrect) {
      setState(() {
        _score++;
      });
    }
    _goToNextQuestion();
  }

  void _goToNextQuestion() {
    setState(() {
      if (_currentQuestionIndex < _questions.length - 1) {
        _currentQuestionIndex++;
        _loadQuestionTimer(); // Start timer for the next question
      } else {
        _quizFinished = true;
        _timer?.cancel();
      }
    });
  }

  void _resetQuiz() {
    setState(() {
      _startQuiz();
    });
  }

  @override
  void dispose() {
    _timer?.cancel(); // Important: Cancel timer to prevent memory leaks
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_quizFinished) {
      return _buildQuizResult();
    } else {
      return _buildQuestionScreen();
    }
  }

  Widget _buildQuestionScreen() {
    final Question currentQuestion = _questions[_currentQuestionIndex];
    return Card(
      margin: EdgeInsets.all(16),
      elevation: 4,
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Align(
              alignment: Alignment.topRight,
              child: Text(
                'Time: \u{23F1} $_secondsRemaining s', // Timer icon
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
            ),
            SizedBox(height: 10),
            Text(
              'Question ${_currentQuestionIndex + 1}/${_questions.length}',
              style: TextStyle(fontSize: 16, color: Colors.grey[700]),
            ),
            SizedBox(height: 10),
            Text(
              currentQuestion.questionText,
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 20),
            ...currentQuestion.options.map((option) => Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: ElevatedButton(
                onPressed: () => _answerQuestion(option),
                style: ElevatedButton.styleFrom(
                  padding: EdgeInsets.symmetric(vertical: 15),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                  ),
                ),
                child: Text(
                  option.text,
                  style: TextStyle(fontSize: 18),
                ),
              ),
            )).toList(),
            SizedBox(height: 20),
            Text(
              'Current Score: $_score',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildQuizResult() {
    return Card(
      margin: EdgeInsets.all(16),
      elevation: 4,
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              'Quiz Finished!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 20),
            Text(
              'Your Score: $_score / ${_questions.length}',
              style: TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
              textAlign: TextAlign.center,
            ),
            SizedBox(height: 30),
            ElevatedButton(
              onPressed: _resetQuiz,
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: 15),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              child: Text(
                'Retake Quiz',
                style: TextStyle(fontSize: 18),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Integrating the Quiz Widget

Finally, integrate your QuizWidget into your main.dart or any other screen in your application.


// lib/main.dart

import 'package:flutter/material.dart';
import 'widgets/quiz_widget.dart'; // Make sure this path is correct

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Interactive Quiz App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Quiz'),
        ),
        body: Center(
          child: QuizWidget(), // Your interactive quiz widget
        ),
      ),
    );
  }
}

Explanation of Key Concepts

  • _QuizWidgetState State Variables:
    • _currentQuestionIndex: Tracks the index of the currently displayed question.
    • _score: Stores the user's correct answers count.
    • _quizFinished: A boolean to determine if the quiz has ended.
    • _timer and _secondsRemaining: Manage the countdown timer for each question.
  • initState and dispose: initState is used to load questions and start the quiz, including the first timer. dispose is crucial for cancelling the Timer to prevent resource leaks when the widget is removed from the widget tree.
  • Timer.periodic: This static method creates a recurring timer that fires an event every specified duration. Here, it updates _secondsRemaining every second.
  • _answerQuestion Logic: When an option is selected, the current timer is cancelled, the score is updated if the answer is correct, and _goToNextQuestion is called.
  • _goToNextQuestion Logic: Advances the _currentQuestionIndex and calls _loadQuestionTimer for the next question. If all questions are answered, _quizFinished is set to true.
  • Conditional UI Rendering: The build method conditionally renders either the question screen (_buildQuestionScreen) or the quiz result screen (_buildQuizResult) based on the _quizFinished state.

Further Enhancements

  • Visual Feedback: Show immediate feedback (e.g., green for correct, red for incorrect) after an answer is selected.
  • Progress Indicator: Add a progress bar or indicator to show how many questions are left.
  • Difficulty Levels: Implement different sets of questions based on difficulty.
  • Question Shuffling: Randomize the order of questions and options.
  • Persistence: Save quiz scores or user progress using shared_preferences or a database.
  • Advanced State Management: For larger apps, consider using Provider, Riverpod, or BLoC for more robust state management.

Conclusion

Building an interactive quiz widget in Flutter is a straightforward process when broken down into manageable components. By leveraging StatefulWidget for state management, Timer.periodic for countdowns, and clear data models, you can create engaging and functional quizzes. This article provides a solid foundation, and you can further expand upon these concepts to build even more sophisticated and feature-rich interactive experiences for your users.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is