Flutter 1부터 배우기 : 나만의 퀴즈 앱 -6 [매개변수 적용, setState 적용]

개발하자 백조·2023년 1월 9일
1
post-thumbnail

아직 5편 named parameter 를 못 봤다면? 보러가기 👀


반갑습니다 여러분! 코딩백조입니다🦢

지난 시간이 길었어요, 그쵸?
named parameter 이라는 새로운 개념을 배우고 적용까지 해봤어요.

이번 시간에는!! 거의 마무리 단계죠?
늘어나는 questionIndex 값을 가지고 실제로 화면을 바꿔볼게요! 🤩
아마 이번 프로젝트의 하이라이트가 아닐까 싶습니다. 👏

📍 [ 6 / 7 ] 매개변수 사용법, setState 사용법 익히기

오늘은 결과 화면 나오기 전까지 마무리 하고,
다음 시간에 결과 화면까지 만들어보고 끝내도록 하겠습니다!

거의 다 왔어요!!! 조금만 힘내봅시다 😉


지난 시간, 버튼을 눌러보면서 questionIndex 가 증가하는 것을 보았어요.
이제 이걸 이용해서 다음 질문으로 넘어가는 걸 배워볼게요!

1. 0 이라는 숫자를 하나 만들고, ( int questionIndex = 0; )
2. 대답 버튼을 누를 때마다 questionIndex 에 1씩 더한 다음에,
3. 우리의 질문/대답 리스트에서 'questionIndex' 번째의 항목을 가져오고,
4. 그걸 기준으로 화면을 다시 그리기

자! 제가 3번이 제일 어려울 것 같다고 말씀 드렸었죠?
questionIndex 번째 항목을 가져오는 게 뭔 뜻인지.. 모르겠어요.
더 쉽게 설명해드릴게요! 😎

질문과 대답 쌍을 저장한 questions 리스트, 혹시 기억 나시나요?
그 리스트에는 총 3개의 질문/대답 Map 이 저장되어 있어요.
'다음 화면으로 넘긴다'는건 사실, '다음 질문/대답을 보여준다'랑 비슷한거 같아요. 안그런가요? 😝
아예 다른 화면을 보여주는 게 아니라도, 똑같은 템플릿 안에서 질문과 대답이 바뀐다면 음.. 충분히 다음 화면으로 가는 느낌일 것 같아요!

그니까 우리는 3개의 개별적인 화면을 만들고 그것들 사이에서 움직이기 보다는,
1개의 화면에서 "질문"과 "대답"의 글씨만 바꿔주도록 할게요!
어짜피 둘의 생김새랑 기능은 똑같은데 더 적은 코드로 짤 수 있거든요 🤩

🤔 근데.. 화면에서 질문과 대답 글씨는 어떻게 바꿔주는데요? 그게 가능해요??

네, 가능합니다!
setState 라는 것이 바로 그걸 가능하게 해주는 명령어에요.

지금은 자세히 배우지 않겠지만, 플러터의 "상태관리"에 대해 더 읽어보시면 좋을거에요.
간단하게 제가 쓴 state가 뭔데? 를 읽어보셔도 좋구요!
여기에 우리가 하는 것과 비슷한 예제가 들어가 있어요. 😌

자! 아무튼, 다시 진도를 나가볼게요.
우리의 첫 번째 화면은, 어떻게 보면 우리의 질문 리스트의 첫 번째 질문/대답 쌍을 기준으로 만들어진 화면이에요.
그렇다면, 대답 버튼을 눌렀을 때 어떻게 동작하면 될까요?
질문 리스트의 두 번째 항목을 기준으로 화면을 다시 그리면 될거에요!
다시 또 버튼을 누르면 세 번째 항목을 기준으로 다시 그리면 되구요.

세 번째 화면에서 다시 버튼을 누르면, 더 이상 보여줄 질문/대답이 없기 때문에 결과 화면으로 이동하면 될 거 같아요!

앞으로의 진행 과정이 조금 이해가 되셨나요? 😉
다 이해 되지 않아도 차근차근 따라오시면 됩니다! 다 알면.. 굳이 이걸 할 필요가 없을테니까요! 😞 어려운 게 당연한거에요.


현재 앱을 실행했을 때 보이는 화면은, 질문 리스트의 첫 번째 항목을 기준으로 만들어진 화면이에요.

더 자세히 볼까요? quiz_screen.dart 로 이동해볼게요!

질문을 보여주는 Text() 위젯 내부의 데이터가 뭔지 볼게요.
questionList[0]["questionText"] 이 부분이 이해가 되시나요?

우리의 질문 리스트의 0번째(첫 번째) 항목 중에서 'questionText'의 value 값을 보여준다는 뜻입니다!

  {
    'questionText': '평화로운 휴일!\n간만에 찾은 자유시간,\n당신은 무엇을 하시겠습니까?',
    'answers': [
      {'text': '나에게 휴식이란 없다. 소맥 달려~!', 'score': 8},
      {'text': '운동은 못참지! 헬스장 가기', 'score': 6},
      {'text': '노래 들으며 산책이나 할까?', 'score': 4},
      {'text': '피곤해.. 누워서 넷플 정주행하기', 'score': 2},
    ],
  },

이게 첫 번째 항목이라는 걸 생각하면 이해가 되네요!

그렇다면, 퀴즈! 두 번째 질문은 어떻게 가져올 수 있을까요?

🧐
생각 해보셨나요?

정답은 바로 questionList[1]["questionText"] 입니다!
아까랑 숫자만 바뀌었죠?
리스트의 1번째 항목의 질문을 가져온다는 뜻이니, 결국은 두 번째 항목인

  {
    'questionText': '카페에서 이상형을 본 당신!\n무엇을 하시겠습니까?',
    'answers': [
      {'text': '말은 부끄러워서 못 걸거 같다. 눈에만 담기', 'score': 2},
      {'text': '가서 말을 걸어본다. 혹시 애인 있으세요?', 'score': 8},
      {'text': '나 오늘 .. 상태 괜찮나? 일단 거울 보기', 'score': 4},
      {'text': '그 사람 앞에 쪽지를 남기고 떠난다.', 'score': 6},
    ],
  },

에서 질문을 가져올거에요.

좋아요! 개념을 거의 다 나갔어요!

대답 버튼을 누를 때마다 questionIndex 가 0에서 1로, 1에서 2로 올라가잖아요,
그 숫자 그대로 questionList[여기]["questionText"] 에 넣으면 될 것 같아요! 🤩

📍3번 나갑니다!

main.dart 에서 정의한 questionIndex 라는 값을 가져와서 quiz_screen.dart 에다가 넣어줄게요!!

지난 시간에 했던 named parameter 을 이용해서 해볼게요.

quiz_screen.dart 안에다가 먼저 questionIndex 를 받을 변수를 만들어줄게요.

  • final int questionIndex;
  • required this.questionIndex;

이 두 개를 넣어주세요!

저는 보기 쉽게 줄바꿈을 많이 해줬구요,
그 결과 8번과 12번 줄에 추가를 했습니다!

플러터는 나열되어 있는 항목 뒤에 붙는 , 를 trailing comma 라고 부르는데요,
required this.questionIndex 뒤에 쉼표를 달고
우클릭 -> "Reformat Code with 'dart format'" 를 누르면 저런 식으로 보기 좋게 정렬해준답니다!
자주 자주 눌러주세요! 😋

이렇게 했으면 또 main.dart 에서 값을 넣어줘야겠죠?
이번에는 questionIndex 변수를 넣어줄게요!

이런 식으로 QuizScreen 위젯에서 questionIndex 매개변수를 입력해주고 값을 전달해준 뒤, 우클릭 후 reformat 까지 해주었습니다!

이제 quiz_screen 에서 나머지 작업을 할 수 있겠어요 🤩


아까 말한 대로, 대답 버튼을 누를 때마다 questionIndex 가 0에서 1로, 1에서 2로 올라갑니다.
questionList[여기]["questionText"] 에 넣어보자고 했었죠?

그대로 진행합니다!!

quiz_screen.dart 에 들어가서 해당 부분을 바꿔볼게요.

질문을 담고 있는 Text 위젯을 볼까요? 👀

Text(questionList[0]["questionText"]),

여기를 바꿔줄게요.
0 대신 questionIndex 를 넣어주세요!

Text(questionList[questionIndex]["questionText"]),

이렇게 바꿔주면 questionIndex 변수의 값이 증가함에 따라 우리의 Text 위젯 내부의 데이터도 바뀔거에요!

좋아요, 이런 느낌으로 대답 버튼의 값도 바꿔줘봅시다! 😉

ElevatedButton(
  onPressed: () => answerPressed(),
  child: Text(questionList[0]["answers"][0]["text"]),
),

여기는 숫자가 2개가 있네요?
과연 어떤 것을 바꿔야 할지 감이 오시나요? 🤔

  {
    'questionText': '평화로운 휴일!\n간만에 찾은 자유시간,\n당신은 무엇을 하시겠습니까?',
    'answers': [
      {'text': '나에게 휴식이란 없다. 소맥 달려~!', 'score': 8},
      {'text': '운동은 못참지! 헬스장 가기', 'score': 6},
      {'text': '노래 들으며 산책이나 할까?', 'score': 4},
      {'text': '피곤해.. 누워서 넷플 정주행하기', 'score': 2},
    ],
  },

questionList 의 첫 번째 항목입니다.

answers 라는 key 값의 첫 번째 항목은

{'text': '나에게 휴식이란 없다. 소맥 달려~!', 'score': 8},

이겠죠!

그러니까 questionList[여기]["answers"][0]["text"]에 들어갈 것은 questionIndex 일 것이고,
questionList[0]["answers"][여기]["text"] 에 들어갈 것은 버튼 순서대로 0 1 2 3 일거에요! 😲

코드를 보여드릴게요. 보면 더 이해가 되실거에요!

ElevatedButton(
  onPressed: () => answerPressed(),
  child: Text(questionList[questionIndex]["answers"][0]["text"]),
),
ElevatedButton(
  onPressed: () => answerPressed(),
  child: Text(questionList[questionIndex]["answers"][1]["text"]),
),
ElevatedButton(
  onPressed: () => answerPressed(),
  child: Text(questionList[questionIndex]["answers"][2]["text"]),
),
ElevatedButton(
  onPressed: () => answerPressed(),
  child: Text(questionList[questionIndex]["answers"][3]["text"]),
),

questionIndex 가 0에서 1, 1에서 2로 변하면 questionList 에서 가져오는 항목이 변할거에요.

questionIndex 가 1일때, 세 번째 버튼의 Text 값은 어떻게 표현할까요?

우리가 만든 questionList 기준으로 분류했어요.

questionIndex 가 1인 경우는 보라색,
그 중 "answers" key 부분은 주황색,
세 번째 버튼(2번째)의 항목은 파란색,
그리고 버튼의 "text" key 부분은 빨간색 입니다. 😲

이런 식으로 차근차근 뜯어보면 이해할 수 있어요!

어떠신가요? ElevatedButton 안에 들어가는 값들이 이제 이해가 되시나요? 😚

1. 0 이라는 숫자를 하나 만들고, ( int questionIndex = 0; )
2. 대답 버튼을 누를 때마다 questionIndex 에 1씩 더한 다음에,
3. 우리의 질문/대답 리스트에서 'questionIndex' 번째의 항목을 가져오고,
4. 그걸 기준으로 화면을 다시 그리기

이제 3번 해결이네요!

한 번 Run 해보겠습니다!!

오잉? 버튼을 눌러도 다음 페이지로 넘어가지 않아요!! 😭

네, 그게 정상입니다!

아직 우리는 "버튼 눌렀을 때 화면 다시 그려줘!" 에 해당하는 코드를 주지 않았거든요. 😅
혹시 그 명령어가 뭔지 아시나요?

📍4번, 마무리!

바로 setState(); 입니다!

버튼을 눌렀을 때 적어줘야 하는 함수니까, main.dart 에 있는 answerPressed() 함수 안에다 적어줄게요!

  void answerPressed() {
    questionIndex++;
    // !!여기다가 적어주세요!!
    print(questionIndex);
  }

setState 를 적다보면 저렇게 자동완성이 뜰 거에요.
엔터를 눌러주면 저렇게 빈 칸이 생기죠?

이 빈칸은 "여기다가 변경되는 값을 적어줄게!" 라는 뜻입니다!
우리는 어떤 값이 변경이 되죠?
네, 바로 questionIndex 에요.
중괄호 안으로 questionIndex++; 를 옮겨줍시다!

  void answerPressed() {
    setState(() {
      questionIndex++;
    });
    print(questionIndex);
  }

이 명령어를 통해 우리는 answerPressed() 함수가 실행될 때마다 변경된 questionIndex 를 기준으로 화면을 다시 그릴 수 있게 되었습니다! 🤩

자, 그러면 다시 Run 해볼게요!

오오 화면이 잘 동작....
엥? 😱
갑자기 에러가 뜨면서 앱이 멈췄어요. 😵

이유를 보니... RangeError 라고 뜨네요. 3 이라는 숫자가 0~2 사이에 속하지 않는다니.. 그게 뭔 뜻이람!

이 에러가 나는 이유는 우리가 questionIndex 를 끊임없이 커지게 했기 때문이에요.
0번째 이후로 1번째, 1번째 이후로 2번째까지는 questionList 에서 질문과 대답 목록을 잘 가져오다가
자 이제 3번째를 내놔! 하니까 에러가 나는거죠! questionList 는 2번째 까지밖에 없으니까요.

이 에러는 다음 시간에 고쳐볼게요.

1. 0 이라는 숫자를 하나 만들고, ( int questionIndex = 0; )
2. 대답 버튼을 누를 때마다 questionIndex 에 1씩 더한 다음에,
3. 우리의 질문/대답 리스트에서 'questionIndex' 번째의 항목을 가져오고,
4. 그걸 기준으로 화면을 다시 그리기

일단 지금은 세 번째 페이지까지 성공적으로 앱이 돌아가는걸 축하합시다!! 🥳
비록 에러가 있지만 아무튼 앱이 원하는 대로 동작하고 있어요.
이 정도 왔으면 정말 잘 따라오신 겁니다! 😎

다음 시간이 마지막이죠?
점수를 기준으로 결과 화면을 출력하는 파트로 마무리하겠습니다.

마지막까지 화이팅이에요!! 🥰


main.dart

import 'package:flutter/material.dart';
import 'package:personal_quiz/screens/quiz_screen.dart';

void main() {
  runApp(
    const MaterialApp(home: MyApp()),
  );
}

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int questionIndex = 0;

  void answerPressed() {
    setState(() {
      questionIndex++;
    });
    print(questionIndex);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("나만의 퀴즈 만들기"),
      ),
      body: QuizScreen(
        answerPressed: answerPressed,
        questionIndex: questionIndex,
      ),
    );
  }
}

quiz_screen.dart

import 'package:flutter/material.dart';
import 'package:personal_quiz/question_list.dart';

class QuizScreen extends StatelessWidget {
  const QuizScreen({
    Key? key,
    required this.answerPressed,
    required this.questionIndex,
  }) : super(key: key);

  final Function answerPressed;
  final int questionIndex;

  
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          const SizedBox(height: 100),
          Text(questionList[questionIndex]["questionText"]),
          const SizedBox(height: 150),
          ElevatedButton(
            onPressed: () => answerPressed(),
            child: Text(questionList[questionIndex]["answers"][0]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(),
            child: Text(questionList[questionIndex]["answers"][1]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(),
            child: Text(questionList[questionIndex]["answers"][2]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(),
            child: Text(questionList[questionIndex]["answers"][3]["text"]),
          ),
        ],
      ),
    );
  }
}
profile
개발자로서 100가지 일을 해보고 싶은 조경현의 개발 블로그

0개의 댓글