Front-End/Flutter

3. <Quiz APP>을 통한 project- Data model/ Dummy Data

sd4beatles 2024. 11. 17. 06:45
  • lib
    |- data|- models
  • |- quiz_question.dart
  • |- questions.dart

1. Create QuizQuestion Class

먼저 Quiz object를 만들기 위한 class를 정의하도록 하자. 우리는 예를 들어 아래와 같은 질문지와 선택지를 만들고 싶다고 하자.

Q: What are the main componetns of Blance Sheet?

A: - Asset

  • Revenue
  • Operating Incom
  • Liability
  • Expeneses

위와 같은 format을 만들려면 우리는 string과 string으로 묶인 List형태가 필요한것을 알 수 있다. 이러한 형태를 class에 정의하고 싶다면, 아래와 같이 정의를 내린다. models/quiz_question.dart에 아래와 같이 정의를 내린다.


class QuizQuestion{

  const QuizQuestion(this.text,this.answers);

  final String text;
  final List<String> answers;

}

위와 같이 QuizQuestion class를 정한 후에, 우리가 원하는 질문과 선택지를 적는 곳은 data/quiz_question.dart에 적시하도록 하자.

import 'package:adv_basics/models/quiz_question.dart';

const questions=[
  QuizQuestion("What is the main componet of Balance Sheet?",
  [
    "Operating Income",
    "Revenue",
    "Expenses",
    "Asset",
  ],
  ),

  QuizQuestion("By which user-application can dierct its operating system to acess the allocated resources? ", 
  [
    "By calling system calls",
    "By activating the internal functions",
    "By using cpu dependencies",
    "By switching its context"
  ]
  )
];

1.2 shuffle

선택지의 순서를 random으로 바꾸는 방법을 알아보도록 하자. 이전에 우리가 앞으로 사용할 ** List.of vs List.from **을 알아보도록 하자.

1.2.1 List.of vs List.from

List.of 와 List.from은 둘 다 기존의 list로 부터 새로운 list로 복사를 하는데 사용한다. 즉, 메모리상에 original list 와 newly created list 둘이 적제되어, 서로 상충되지 않는다. 겉보기에 같아보이는 이 옵션들에게 큰 차이점이 존재하는데, 이는 아래와 같다.

  • List.of는 complier시에 적시된 data type만 따르도록 허용된다.즉, List이면, 그 모든 원소들이 int어야 한다.
  • List.from은 데이터 변형이 상당히 유연하다. 유연성을 가진다는 것은, 많은 세심함이 필요하다.
  • void main() { List<int>data=[1,2,3,4]; List<double> data2=List.from(data.map((e)=>e.toDouble())); for(int i=0;i<data2.length;i++){ print(data2[i]); }

}

  List<int>data1=List.of(data.map((e)=>e.toInt()));                                

1.2.2 Shuffling the selections

class 멤버함수로서 아래와 같은 method를 하나 더 추가가한다.

class QuizQuestion{

  const QuizQuestion(this.text,this.answers);

  final String text;
  final List<String> answers;


  List<String> getShuffledAnswers(){
    //call built-in method 'shuffle'
    final shuffledList=List.of(answers);
    shuffledList.shuffle();
    return shuffledList;

  }

}

2. Create QuestionScreen Class

이번에는 우리가 실질적으로 question과 선택지를 집어넣을 수 있는 screen content를 만들어 보도록 합시다.

import 'package:flutter/material.dart';

class QuestionScreen extends StatefulWidget{
  const QuestionScreen({super.key});

  @override
  State<QuestionScreen>createState(){
    return _QuestionsScreenState();
  }
}


class _QuestionsScreenState extends State<QuestionScreen>{
  @override
  Widget build(context){
    return SizedBox(
      width:double.infinity,
      child:Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
      //child 01
      Text(
        "The questions are ...",
        style:TextStyle(
          color: Colors.white,
          fontSize: 25,

        )),
      //child 02
      const SizedBox(height:30),
      //child 03
      ElevatedButton(onPressed: (){}, child: Text("Answer01"),
      ),
      const SizedBox(height:30),
      //child 04
      ElevatedButton(onPressed: (){}, child: Text("Answer02")),
      const SizedBox(height:30),
      //child05
      ElevatedButton(onPressed: (){}, child: Text("Answer03"))

    ],)
    );
  }
}

위에 코드를 잠시 살펴보면 모든 ElevatedButton이 반복적으로 되어 있어, 가독성을 떨어뜨리는 것을 알 수 있다. 그러므로 우리는 이렇게
반복적인 형식을 하나의 template class로 만들어서 간소화시키는 작업을 실행하도록 하자.

  • lib
    |-main.dart
    |-answer_button.dart

answer_button.dart 파일에 AnswerButton class는 StatelessWidget constructor에서 두 개의 argument를 받도록 지정해놓자. 이때, String argument를 받을 수 있는 text 그리고 void function을 받을 수 있는 두 번째 인자를 지정하도록 하자.

import 'package:flutter/material.dart';

class AnswerButton extends StatelessWidget {
  //constructior - acccept the string text argumnet as the first and void function as the second
  //AnswerButton(this.answerText,this.onTap,{super.key}); 

  AnswerButton({
    super.key,
    required this.answerText,
    required this.onTap
  });


  final String answerText;
  final void Function() onTap;



  @override
  Widget build(BuildContext context){
    return ElevatedButton(
      onPressed: onTap, 
      style: ElevatedButton.styleFrom(),
      child: Text(answerText)) ;
  }

}

❗ constructor에서 인자를 받는 방법

constructor에서 인자를 받는 방법은 두 가지가 있다. 첫번째로 positional argument로 받거나, named argument로 받을 수 있다.
다만, curly brace안에 있는 named 인자 같은 경우에는 근본적으로 optional이기 때문에, 꼭 인자를 받아야 할 경우는 required 키워드를
사용해야한다.

위에 AnswerButton class를 생성함으로써, 우리는 기존의 answer_button.dart의 private class를 아래와 같이 다시 수정할 수 있다.

class _QuestionsScreenState extends State<QuestionScreen>{
  @override
  Widget build(context){
    return SizedBox(
      width:double.infinity,
      child:Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
      //child 01
      Text(
        "The questions are ...",
        style:TextStyle(
          color: Colors.white,
          fontSize: 25,

        )),
      //child 02
      const SizedBox(height:30),
      //child 03
      AnswerButton(answerText:"Answer 1", onTap:(){},),
      const SizedBox(height:30),
      //child 04
      AnswerButton(answerText:"Answer 2", onTap:(){},),
      const SizedBox(height:30),
      //child05
        AnswerButton(answerText:"Answer 3", onTap:(){},),

    ],)
    );
  }
}

3. AnswerButton class (Additional Features)

이전 section에선 우리는 AnswerButton class를 생성하였다. 이렇게 class widget을 만들게 되면, 추가적인 option을 집어넣어서 이 class에 속한 모든 block들이 영향을 받게 된다.(일일이 반복해서 쓸 필요없이, 범용적으로 쓰게 되어 가독성을 높힌다는 말임)

일단, AnswerButton의 style을 좀 더 세련되게 만들기 위해서, code를 축가해보도록 하자.

import 'package:flutter/material.dart';

class AnswerButton extends StatelessWidget {
  //constructior - acccept the string text argumnet as the first and void function as the second
  //AnswerButton(this.answerText,this.onTap,{super.key}); 

  AnswerButton({
    super.key,
    required this.answerText,
    required this.onTap
  });


  final String answerText;
  final void Function() onTap;



  @override
  Widget build(BuildContext context){
    return ElevatedButton(
      onPressed: onTap, 
      style: ElevatedButton.styleFrom(
        padding:EdgeInsets.symmetric(vertical: 10, horizontal: 60) , 
        backgroundColor: Color.fromARGB(255, 31, 3, 48),
        foregroundColor: Colors.white,
        shape:RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(40),

        ),

      ),
      child: Text(answerText)) ;
  }

}