Trivia Flutter App Project with Source Code

Flutter is a free and open-source UI framework from Google for building native mobile apps.

trivia_app

An trivia/quiz app made in flutter for answering fun trivia questions.

This app make use of Opentdb api (https://opentdb.com/) for fetching trivia questions.

What is Opentdb?

The Open Trivia Database is a user-contributed trivia database that is available for free. The Open Trivia Database API provides a set of trivia questions that may be filtered by category, difficulty, type, and encoding. The response is in JSON format.

Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Source Code

  • Create a flutter app using flutter create trivia_app command in the terminal.
  • Inside the trivia_app folder, go inside the lib folder. The lib folder is where we place all the important stuff of our flutter application.
  • Inside the lib create the following .dart files and folder:
  • lib/main.dart
import 'package:flutter/material.dart';
import 'package:trivia_app/first_page.dart';
import 'package:trivia_app/theme/theme_data.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: CustomTheme.appLightTheme(context),
      darkTheme: CustomTheme.appDarkTheme(context),
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.dark,
      home: FirstPage(),
    );
  }
}
  • lib/api_trivia.dart
// import 'dart:developer';
// import 'package:http/http.dart' as http;
//
// Future<void> getStates() async {
//   Uri url = Uri.parse("https://opentdb.com/api.php?amount=10");
//
//   var response = await http.get(url);
//   if (response.statusCode == 200) {
//     log('api worked ${response.body}');
//     var body = response.body;
//   } else {
//     log('api request failed ${response.body}');
//
//     return null;
//   }
// }

import 'dart:developer';
import 'dart:convert';
import 'package:trivia_app/model/questions.dart';
import 'package:http/http.dart' as http;
import 'package:trivia_app/model/questions.dart';

class ApiTrivia {
  Future<List<Results>?> getStates() async {
    Uri url = Uri.parse("https://opentdb.com/api.php?amount=10&difficulty=easy&type=multiple");

    var response = await http.get(url);
    if (response.statusCode == 200) {
      log('api worked ${response.body}');
      var body = response.body;
      var statesJsonArray = json.decode(body)['results'];

      try {
        List<Results> results =
            (statesJsonArray as List).map((e) => Results.fromJson(e)).toList();

        return results;
      } catch (e) {
        log('try failed $e');
      }
    } else {
      log('api request failed ${response.body}');

      return null;
    }
  }
}
  • lib/first_page.dart
import 'dart:ui';
import 'package:google_fonts/google_fonts.dart';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:trivia_app/second_page.dart';

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

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

class _FirstPageState extends State<FirstPage> {
  final ButtonStyle raisedButtonStyle = ElevatedButton.styleFrom(
    primary: Colors.blueGrey[800],
    minimumSize: Size(88, 36),
    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.all(Radius.circular(2)),
    ),
  );
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            SizedBox(
              height: MediaQuery.of(context).size.height * 0.45,
            ),
            Text(
              'TRIVIA APP',
              textAlign: TextAlign.center,
              style: GoogleFonts.rubik(
                fontSize: 50,
                fontWeight: FontWeight.w500,
              ),
            ),
            SizedBox(
              height: MediaQuery.of(context).size.height * 0.45,
              width: 300,
              child: Center(
                child: ElevatedButton(
                  style: raisedButtonStyle,
                  onPressed: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => SecondPage()),
                    );
                  },
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        'TAKE QUIZ',
                      ),
                      SizedBox(
                        width: 10,
                      ),
                      Icon(
                        Icons.arrow_forward_rounded,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

void _ontapped() {}
  • lib/options.dart
import 'package:flutter/material.dart';
import 'package:trivia_app/question_pageview.dart';
import 'package:trivia_app/model/shuffleanswers.dart';

class Options extends StatefulWidget {
  final List wrongRightList;
  final OptionSelectedCallback onOptionsSelected;
  final int selectedPosition;
  final int index;
  //List finalWrongrightlist = [];
  Options({
    required this.wrongRightList,
    required this.onOptionsSelected,
    required this.selectedPosition,
    required this.index,
  });

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

class _OptionsState extends State<Options> {
  int selectedIndex = 99;

  @override
  void initState() {
    super.initState();
    // Shuffleright a = Shuffleright(
    //     Shuffler: (list) {
    //       widget.finalWrongrightlist = list;
    //     },
    //     wrongright: widget.wrongright);
    // a.mix();
    selectedIndex = widget.selectedPosition;
    print("positio here is $selectedIndex");
  }

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: ListView.builder(
        itemCount: widget.wrongRightList[widget.index].length,
        itemBuilder: (context, position) {
          return Card(
            elevation: 2,
            margin: EdgeInsets.symmetric(vertical: 6),
            child: CheckboxListTile(
              checkColor: Colors.blueGrey[800],
              activeColor: Colors.blueGrey[100],
              selectedTileColor: Colors.blueGrey[800],
              selected: selectedIndex == position,
              title: Text(
                  '${widget.wrongRightList[widget.index].elementAt(position)}'),
              value: selectedIndex == position,
              onChanged: (bool? newValue) {
                widget.onOptionsSelected(
                    widget.wrongRightList[widget.index].elementAt(position));
                setState(
                  () {
                    selectedIndex = position;
                    // widget.selectedPosition = position;
                  },
                );
              },
            ),
          );
        },
      ),
    );
    ;
  }
}
  • lib/question_pageview.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:trivia_app/options.dart';
import 'package:trivia_app/question_pageview.dart';
import 'package:trivia_app/scorepage.dart';
import 'package:trivia_app/model/shuffleanswers.dart';

typedef void OptionSelectedCallback(String option);

class QuestionsPageView extends StatefulWidget {
  final List results;
  final List wrongRightList;

  QuestionsPageView({required this.results, required this.wrongRightList});

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

class _QuestionsPageViewState extends State<QuestionsPageView> {
  List<String> _userAnswerList = [];
  List<String> correctanswerlist = [];
  int currentPagePosition = 0;
  PageController _controller = PageController();

  @override
  void initState() {
    super.initState();
    _userAnswerList.addAll(widget.results.map((e) => ""));
    correctanswerlist = [];
    for (int i = 0; i < widget.results.length; i++) {
      correctanswerlist.add(widget.results.elementAt(i).correct_answer);
    }

    // for(int i =0; i < widget.results.length; i++) {
    //   _userAnswerList.add("");
    // }
  }

  @override
  Widget build(BuildContext context) {
    final TextStyle subtitle = Theme.of(context).textTheme.subtitle1!;
    final TextStyle body = Theme.of(context).textTheme.bodyText1!;

    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Container(
            width: MediaQuery.of(context).size.width * 0.9,
            height: MediaQuery.of(context).size.height * 0.7,
            child: Center(
              child: PageView.builder(
                controller: _controller,
                itemCount: widget.results.length,
                pageSnapping: true,
                onPageChanged: (position) {
                  currentPagePosition = position;
                },
                itemBuilder: (context, index) {
                  String userAnswer = _userAnswerList[index];
                  int checkedOptionPosition =
                      widget.wrongRightList[index].indexOf(userAnswer);

                  return Container(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 18.0, vertical: 12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          SizedBox(
                            height: 10,
                          ),
                          Text(
                            'Question ${index + 1}',
                            style: GoogleFonts.oswald(
                                textStyle: subtitle, color: Colors.yellow[800]),
                          ),
                          SizedBox(
                            height: 15,
                          ),
                          Text(
                            '${widget.results.elementAt(index).question}',
                            style:
                                GoogleFonts.lato(textStyle: body, fontSize: 20),
                          ),
                          SizedBox(
                            height: 20,
                          ),
                          Options(
                            index: index,
                            wrongRightList: widget.wrongRightList,
                            selectedPosition: checkedOptionPosition,
                            onOptionsSelected: (selectedOption) {
                              print("selected item is $selectedOption");
                              _userAnswerList[currentPagePosition] =
                                  selectedOption;
                              print(_userAnswerList.toList().toString());
                            },
                          ),
                          SizedBox(
                            height: 60,
                          )
                        ],
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
          Center(
            child: Container(
              width: MediaQuery.of(context).size.width * 0.9,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    mainAxisSize: MainAxisSize.max,
                    children: [
                      TextButton(
                        style: ElevatedButton.styleFrom(
                          padding: EdgeInsets.all(20),
                        ),
                        onPressed: () {
                          _controller.animateToPage(
                            currentPagePosition == 0
                                ? 0
                                : currentPagePosition - 1,
                            duration: Duration(milliseconds: 100),
                            curve: Curves.easeIn,
                          );
                        },
                        child: Text(
                          'Previous',
                          style: GoogleFonts.lato(
                              textStyle: body,
                              fontSize: 15,
                              color: Colors.yellow[800]),
                        ),
                      ),
                      ElevatedButton(
                        style: ElevatedButton.styleFrom(
                          padding: EdgeInsets.all(15),
                          primary: Colors.yellow[800],
                        ),
                        onPressed: () {
                          _controller.animateToPage(
                              currentPagePosition == widget.results.length - 1
                                  ? currentPagePosition
                                  : currentPagePosition + 1,
                              duration: Duration(milliseconds: 100),
                              curve: Curves.easeIn);
                        },
                        child: Text(
                          'Next',
                          style: GoogleFonts.lato(
                              textStyle: body,
                              fontWeight: FontWeight.bold,
                              fontSize: 15,
                              color: Colors.blueGrey[800]),
                        ),
                      ),
                    ],
                  ),
                  SizedBox(
                    height: 25,
                  ),
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                        padding: EdgeInsets.all(10),
                        primary: Colors.blueGrey[800],
                        fixedSize:
                            Size(MediaQuery.of(context).size.width * 0.7, 50)),
                    onPressed: () {
                      Navigator.pushAndRemoveUntil(
                          context,
                          MaterialPageRoute(
                            builder: (context) => ScorePage(
                              useranswerlist: _userAnswerList,
                              correctanswerlist: correctanswerlist,
                            ),
                          ),
                          (route) => false);
                    },
                    child: Text(
                      'SUBMIT',
                      style: GoogleFonts.lato(
                        textStyle: body,
                        fontSize: 17,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}
  • lib/scorepage.dart
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:trivia_app/second_page.dart';

class ScorePage extends StatefulWidget {
  final List useranswerlist;
  final List correctanswerlist;
  const ScorePage(
      {Key? key, required this.useranswerlist, required this.correctanswerlist})
      : super(key: key);

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

class _ScorePageState extends State<ScorePage> {
  int count = 0;
  int total = 0;

  @override
  void initState() {
    for (int i = 0; i < widget.correctanswerlist.length; i++) {
      if (widget.correctanswerlist.elementAt(i) ==
          widget.useranswerlist.elementAt(i)) {
        count++;
      }
    }
    count *= 10;
    total = widget.correctanswerlist.length * 10;
  }

  @override
  Widget build(BuildContext context) {
    final TextStyle body = Theme.of(context).textTheme.bodyText1!;

    return Scaffold(
        appBar: AppBar(
          elevation: 0.0,
          backgroundColor: Colors.transparent,
          centerTitle: true,
          title: Text('Trivia Result'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              padding: EdgeInsets.all(48),
              decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.blueGrey[800],
                  boxShadow: [
                    BoxShadow(
                      color: Colors.white.withAlpha(60),
                      blurRadius: 6.0,
                      spreadRadius: 4.0,
                    ),
                  ]),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Center(
                    child: Text(
                      "Score",
                      style: GoogleFonts.lato(textStyle: body, fontSize: 30),
                    ),
                  ),
                  Center(
                    child: Text(
                      '${count} / ${total}',
                      style: GoogleFonts.lato(textStyle: body, fontSize: 28),
                    ),
                  ),
                ],
              ),
            ),
            SizedBox(
              height: 90,
            ),
            Container(
              width: 200,
              child: Center(
                child: ElevatedButton(
                  style: ElevatedButton.styleFrom(
                    padding: EdgeInsets.all(20),
                    primary: Colors.blueGrey[800],
                  ),
                  onPressed: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => SecondPage(),
                      ),
                    );
                  },
                  child: Text(
                    'Retake Test',
                    style: GoogleFonts.lato(textStyle: body, fontSize: 20),
                  ),
                ),
              ),
            ),
            SizedBox(
              height: 40,
            )
          ],
        ));
  }
}
  • lib/second_page.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:trivia_app/api_trivia.dart';
import 'package:trivia_app/model/questions.dart';
import 'dart:math';

import 'package:trivia_app/question_pageview.dart';
import 'package:trivia_app/model/shuffleanswers.dart';

//todo change this name
typedef void Randomise(List options);

class SecondPage extends StatefulWidget {
  SecondPage({Key? key}) : super(key: key);
  List wrongRightList = [];

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

class _SecondPageState extends State<SecondPage> {
  final ApiTrivia _apitrivia = ApiTrivia();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        elevation: 0.0,
        backgroundColor: Colors.transparent,
        centerTitle: true,
        title: const Text('Trivia questions'),
      ),
      body: _futureWidget(),
    );
  }

  _futureWidget() {
    return FutureBuilder(
      future: _apitrivia.getStates(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          List results = snapshot.data as List;
          ShuffleRight(
              result: results,
              Shuffler: (options) {
                widget.wrongRightList = options;
              });

          return QuestionsPageView(
              results: results, wrongRightList: widget.wrongRightList);
        } else {
          return Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}
  • lib/model/questions.dart
class Results {
  String? question;
  String? correct_answer;
  List<dynamic> incorrect_answers;

  Results({this.question, this.correct_answer, required this.incorrect_answers});

  Results.fromJson(Map<String, dynamic> mapOfJson)
      : question = mapOfJson["question"],
        correct_answer = mapOfJson["correct_answer"],
        incorrect_answers = mapOfJson["incorrect_answers"];
}
  • lib/model/shuffleanswers.dart
import 'package:trivia_app/question_pageview.dart';
import 'package:flutter/material.dart';
import 'package:trivia_app/second_page.dart';

import '../options.dart';

class ShuffleRight {
  final List result;
  final Randomise Shuffler;
  final List wrongRightList = [];

  ShuffleRight({required this.result, required this.Shuffler}) {
    wrongRightList.addAll(result.map((e) => []));
    for (int i = 0; i < result.length; i++) {
      List wrong = result.elementAt(i).incorrect_answers;
      List right = [result.elementAt(i).correct_answer];
      wrongRightList[i] = wrong + right;
      wrongRightList[i].shuffle();
    }
    Shuffler(wrongRightList);
  }
}
  • lib/theme/theme_data.dart
import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

class CustomTheme {
  static ThemeData appLightTheme(BuildContext context) {
    return ThemeData(
      brightness: Brightness.light,
      // textTheme: GoogleFonts.(Theme.of(context).textTheme)
    );
  }

  static ThemeData appDarkTheme(BuildContext context) {
    return ThemeData(
      brightness: Brightness.dark,
      // textTheme: GoogleFonts.latoTextTheme(),
    );
  }
}

Inside the App

Screenshot
Screenshot
Screenshot
Screenshot

GitHub Repository

Source Code: trivia_app.

SHARE Trivia Flutter App Project with Source Code

You may also like...

Leave a Reply

Your email address will not be published.

Share