Flutter Study-2주차(QuizApp Module Summary)

CHO WanGi·2023년 10월 25일

Flutter

목록 보기
4/27

Quiz App

Flutter Render Conditionally, Lift-State-up(props)

조건부 렌더링

class _QuizState extends State<Quiz> {
  List<String> selectedAnswers = [];
  var activeScreen = 'start-screen';

  void switchScreen() {
    setState(() {
      activeScreen = 'questions-screen';
    });
  }

  void chooseAnswer(String answer) {
    selectedAnswers.add(answer);

    if (selectedAnswers.length == questions.length) {
      setState(() {
        activeScreen = 'results-screen';
      });
    }
  }

  void restartQuiz() {
    setState(() {
      selectedAnswers = [];
      activeScreen = 'questions-screen';
    });
  }

activeScreen에 따른 다른 Ui를 보여주는 코드.

Lifting up State

  • 만약 두 개의 위젯이 동일한 State를 요구한다면?
    ➡ 두개의 부모(상위)위젯으로 State를 올려서 아래 하위 위젯으로 데이터를 전달하는 것이 효율적
// Quiz.dart(상위 위젯)
Widget build(context) {
    Widget screenWidget = StartQuiz(switchScreen);

    if (activeScreen == 'questions-screen') {
      screenWidget = QuestionScreen(
        onSelectAnswer: chooseAnswer,
      );
    }

    if (activeScreen == 'results-screen') {
      screenWidget = ResultsScreen(
        chosenAnswers: selectedAnswers,
        onRestart: restartQuiz,
      );
    }

상위 위젯인 Quiz.dart는
State를 StartQuiz에 switchScreen 인자를 통해 전달하고,
if 연산자를 활용한 조건문 활용을 통해 QuestionScreen에 사용자의 답변 선택내용(state)을 전달한다.

StartQuiz

class StartQuiz extends StatelessWidget {
  const StartQuiz(this.startQuiz, {super.key});

  final void Function() startQuiz; // 인스턴스 화

조건에 따라 렌더링할 버튼은 StartQuiz에 존재
따라서 class에서 조건에 따른 변경을 가능하게 하는 메서드를 내려받아서
버튼에 해당 함수를 달아주어야 함.

return 값의 type을 인수와 맞추어야 하는데, return 이 없기에 void
StartQuiz라는 함수에 pointer를 얻은 개념 -> Props 개념과 비슷
생성자 함수가 props에 명시된 값을 this.로 받아서 생성 -> 인스턴스 화

QuestionScreen

class QuestionScreen extends StatefulWidget {
  const QuestionScreen({
    super.key,
    required this.onSelectAnswer,
  });

  final void Function(String answer) onSelectAnswer;

startQuiz와 마찬가지, 다만,
onSelectAnwer 옵션으로 유저가 선택한 답변을 전달하기때문에,
생성자 함수 생성시 인수로 가져와야함
required 자료형을 통해 Null Saftey 보장

initState

  • State 클래스가 제공하는 함수
class _QuizState extends State<Quiz> {
  Widget? activeScreen;

  
  void initState() {
    activeScreen = StartScreen(swichScreen);
    super.initState();
  }

이렇게 State가 제공하는 iniTState함수를 override.
따라서 State클래스를 상속받는 시점에 선언 -> pointing 시 선언 이후 인스턴스화

?

Optional operator -> null일 수 있다는 것을 의미
initState가 QuizState로 부터 호출시 실행, 컴파일시에는 null

Widget Life Cycle

  1. initState() : flutter 에 의해 실행, StatefulWidget의 2번째 class의 State객체가 생성될때 실행

  2. build(): 위젯이 처음 build 함수가 실행될때, setState에 의해 실행

  3. dispose(): 위젯이 삭제되기 전에 실행-> ex. 조건부 렌더링

위젯 사이 함수 전달

Widget build(context) {
    Widget screenWidget = StartQuiz(switchScreen);

    if (activeScreen == 'questions-screen') {
      screenWidget = QuestionScreen(
        onSelectAnswer: chooseAnswer,
      );
    }
    
   

Quiz에서 QuestionScreen으로 onSelectAnswer 에 chooseAnswer()를 달아서 전달

class QuestionScreen extends StatefulWidget {
  const QuestionScreen({
    super.key,
    required this.onSelectAnswer,
  });

  final void Function(String answer) onSelectAnswer;

  
  State<QuestionScreen> createState() {
    return _QuestionScreenState();
  }
}

class _QuestionScreenState extends State<QuestionScreen> {
  var currentQuestionIndex = 0;
  void answerQuestion(String selectedAnswer) {
    widget.onSelectAnswer(selectedAnswer);
    setState(() {
      currentQuestionIndex++;
    });
  }

전달받은 chooseAnswer를 담은 onSelectAnswer 값은
answerQuestion() 함수 안에서 실행.

for loop & map메서드

//result_screen.dart
for (var i = 0; i < chosenAnswers.length; i++) {
      summary.add(
        {
          'question_index': i,
          'question': questions[i].text,
          'correct_answer': questions[i].answers[0],
          'user_answer': chosenAnswers[i]
        },
      );
    }
  • 다른 언어의 for반복문과 동일한 기능,
  • 전개연산자와 map 메서드 활용으로 갈음 가능

    💡 Iterable<T> 라는 인터페이스 타입 인스턴스를 반환하기때문에, 실제 사용시에는 .toList() 함수로 리스트 타입 형태로 바꾸어 주어야 위젯 내에서 사용가능

  final List<Map<String, Object>> summary = [];

    for (var i = 0; i < chosenAnswers.length; i++) {
      summary.add(
        {
          'question_index': i,
          'question': questions[i].text,
          'correct_answer': questions[i].answers[0],
          'user_answer': chosenAnswers[i]
        },
      );
    }

    return summary;

다만 강의에선, summary라는 배열을 만들고, 여기에 add 메서드를 써서 for문의 내용을 집어넣어서 리스트로 만들어버리는 방식을 활용.

add 메서드

void main() {
  // list 생성자 사용
  var vegetables = List.unmodifiable([]);
  
  // 문자열을 사용하여 list 생성
  var fruits = ['apples', 'oranges'];
  
  // list에 값 추가하기
  fruits.add('bananas');
  print(fruits); // [apples, oranges, banana]
  • 리스트에 값을 추가할때 사용

where 메서드

  final numCorrectQuestions = summaryData.where((data) {
      return data['user_answer'] == data['correct_answer'];
    }).length;
  • filter 의 개념과 비슷
    위 코드에선 정답 구현시 활용

TypeCasting

  • as 키워드 사용

Expanded

  • Column,Row 위젯은 무한하게 넓이를 가져감-> 이때 화면 밖으로 삐져나가면 에러발생
    -> 이를 방지하는 역할
    위젯 내 가용한 최대 넓이(=부모 넓이)를 차지하여 에러를 방지

🚨 항상 오타를 조심하자!

위에서 말한 에러는 이렇게 화면이 시뻘겋게 보이는 게 아니라 리스트의 내용은 보이는데,
이건 아예 null 값이 전달되어 시뻘겋게 지금 너가 뭔가 잘못되었다고 단박에 알아채도록 화면을 꽉 채우는 에러가 나왔다.

이유는 데이터를 question_index로 넘겼는데
result_screen file 에서는 question-index로 되어있어서, 결과값이 없는데 결과값을 출력하라고 하니... 당연히 에러가 발생할 수 밖에 없었다.

Expanded문제인지 아니면 SingleChildScrollView의 문제인지 먼저 찾아봤는데
그쪽 문제가 아니고 오타...문제였다니.. 너무나 허무했다.

profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글