Flutter 1부터 배우기 : 나만의 퀴즈 앱 -7 [list.length, 삼항연산자]

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

아직 6편 화면전환 마무리를 못 봤다면? 보러가기 👀


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

지난 시간으로 화면 전환을 모두 구현했어요!
비록 마지막 화면 이후로는 에러가 나지만, 그건 큰 문제가 아니에요.

오늘이 마지막 시간이죠! 오늘은 지난 시간의 에러 해결과 함께 결과 화면을 보여주도록 할게요. 😉

📍 [ 7 / 7 ] RangeError 해결, 점수 저장하기, 결과 화면 이동하기

이번에는 내용이 많아보이죠? 그래도 다 연결된 내용이라 한 번에 진도를 나가도록 할게요!

드디어 마지막이에요.. 조금만 더 화이팅! 👏


자, 이 3개가 어떻게 연결이 되어있는지 한번 볼까요?

마지막 문제에서 대답 버튼을 누르면 "결과 화면으로 이동" 하며 "에러가 해결이" 될거에요. 그리고 "결과 화면" 안에는 "점수에 따른 멘트" 가 있겠죠?

그니까 결국 대답을 누를 때마다 점수를 저장한 후, 마지막 버튼에서는 에러가 나지 않게 결과화면으로 이동을 하고, 결과 화면은 점수를 바탕으로 화면이 그려질거에요.

그러면 이 순서대로 작업을 해볼게요! 😎

  1. 점수를 저장하고
  2. 마지막 문제 이후로는 결과 화면으로 이동한 후
  3. 점수를 바탕으로 화면을 그리기

바로 시작합니다! 💨

📍 1번 시작!

점수를 저장하는 방법, 생각보다 어렵지 않아요. 😝
questionIndex 와 거의 동일하게 동작합니다!

처음에 0 으로 시작하는 변수를 하나 만들고,
버튼을 누를 때마다 그 대답에 해당하는 점수를 더해주면 될거에요.

그러면 먼저 변수를 만들어줄게요!

main.dart 로 넘어가서 questionIndex 아래에 써줍시다.
이름은.. 흠 대충 totalScore 정도로 할까요?

제 main.dart 에서는 19번째 줄에다가 적어주었습니다.

자, 이제 점수를 더하는 로직을 추가해볼까요?

이 버튼은 몇점 짜리인지 어떻게 알 수 있을까요?
우리가 써놓은 곳에서 찾으면 되겠죠!

{'text': '말은 부끄러워서 못 걸거 같다. 눈에만 담기', 'score': 2},

questionList 를 보면 text: 옆에 score: 이라고 해서 점수를 적는 부분이 있었어요.
거기서 점수를 가져오면 될거에요! 😌

흠.. 🤔 근데 어떻게 불러와야 할까요?
questionList 에 접근 할 수 있어야 될텐데요, 그쵸?

오! 생각해보니 우리는 quiz_screen.dart 에서 questionList 를 불러오기 해서 쓰고 있었어요. 그쵸?
거기서 text 값을 가져왔었잖아요!
그거랑 똑같이 score 값을 가져오면 될거 같아요.

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

근데.. 어느 자리에다가 점수를 넣어줘야 하죠?

매개변수를 사용해보는건 어떨까요? 😲
answerPressed( 4 ) 이런 식으로 괄호 안에다가 점수를 넣어주는 식으로 한다면 편할 거 같아요!

answerPressed 안에다가 text 처럼 score 을 넣어준다면 우리가 원하는 대로 동작할 거에요.

첫 번째 ElevatedButton 안을 이렇게 바꿔보세요.

() => answerPressed(questionList[questionIndex]["answers"][0]["score"]),

이게 제대로 동작한다면 main.dart 에서 저 값을 totalScore 에다 저장해줄거에요! 😚

이 값을 받으려면 answerPressed() 함수에서 매개변수를 정의해야겠죠?

main.dart 로 넘어갑시다!

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

이번에는 positional parameter 을 만들거에요.
근데 큰 차이 없어요! named 는 {} 를 필요로 하지만 positional 은 그냥 괄호 안에 넣으면 돼요. void answerPressed(int score) 이렇게요! 😌

그 다음, totalScore 변수에 그 값을 더해주면 되겠죠?

이 점수는 현재 화면에 영향을 미치지 않으니까 setState 내부에 넣지는 않을게요.

  void answerPressed(int score) {
    setState(() {
      questionIndex++;
    });
    totalScore += score;
    // or
    totalScore = totalScore + score;
    print(totalScore);
  }

편하신 대로 둘 중 하나를 적어주세요!
print 문 안에 있던 questionIndex 를 totalScore 로 바꿨어요. 이제 index 가 잘 동작하기 때문이죠! 😎

자, 그러면 Run 하고 첫 번째 버튼을 누르면 totalScore 이 변하는지 봅시다!

오오! 첫 번째 버튼을 누르면 점수들이 더해지면서 콘솔에 숫자들이 보여요.

마지막 화면에서 에러 나기 전에 숫자가 8 10 이렇게 커지는게 보이시나요?

잘 되고 있다는 뜻이네요!
quiz_screen.dart 로 넘어가서 나머지 함수에도 매개변수를 넣어줍시다!

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

순서대로 0번째 1번째 2번째 3번째 인것 보이시죠?
중간 숫자도 꼭 맞춰주셔야 합니다!!

이렇게 하고 다시 Run 해주면 어떤 버튼을 눌러도 totalScore 값이 더해지면서 출력 되는 걸 볼 수 있어요 🥳

1번 완료입니다!!!

1. 점수를 저장하고
2. 마지막 문제 이후로는 결과 화면으로 이동한 후
3. 점수를 바탕으로 화면을 그리기

자, 2번은 엄청 간단해요.
마지막 질문/대답이 뭔지를 알아내기만 하면 되거든요!

0번째 1번째 2번째까지는 괜찮은데 questionIndex 가 3이 되면 안되는거잖아요.
그러면 3 보다 작으면 되는거겠네요. 내 질문의 개수보다 작으면 돼요!

근데, 내 질문/대답의 수를 어떻게 알아낼 수 있을까요? 🧐


Dart 에는 리스트와 관련된 함수가 많이 있어요.
우리의 질문/대답 리스트에는 3개의 Map 이 저장되어 있어요.

그렇다면 questionList 에 몇 개의 항목이 있는 지 알려주는 함수가 있으면 좋을 것 같아요! 그쵸?

바로 그게 .length 랍니다!!

.length

The number of objects in this list.

라고 설명되는 이 함수는 리스트 안에 있는 항목의 개수를 알려줍니다 🤩
만약 questionList.length 를 입력하면 어떤 숫자가 나올까요?
바로 3 입니다!

이 함수를 사용하면 될거같아요. 그쵸?

0번째 1번째 2번째 까지는 괜찮지만 3번째 부터는 안되는 조건!
조건문을 작성할게요.

조건문이라고 하면 if else 를 생각하기 쉬운데,
더 간단하고 쉬운 연산자가 있답니다!!

삼항 연산자 () ? :

제가 정말 좋아하는 연산자에요 😍

(괄호) 안에 참/거짓의 조건을 쓰고,
? 뒤에 "참 인 경우" 에 해당하는 결과를,
: 뒤에 "거짓 인 경우" 에 해당하는 결과를 써주면 됩니다.

(is_happy) ? print("happy!") : print("not happy.")

처럼 써줄 수 있어요!
이걸 사용하면 더 간편하게 조건을 걸어줄 수 있죠.

(퀴즈 중이라면) ? 퀴즈 화면 : 결과 화면

어떤가요? 이렇게 한 줄로 조건문을 걸어줄 수 있답니다!

🗣 알겠어요. 알겠는데, 그래서 이걸 어디다 쓰라고요? 어떻게 사용하는데요?

우리의 QuizScreen 이라는 위젯이 어디에 있죠?
main.dart 의 아래에 있어요.
이 부분에 넣어줄거에요.

지금 body: 안에 QuizScreen() 이 들어가있죠?

2번을 여기서 진행합니다.

📍 2번 시작!

1. 점수를 저장하고
2. 마지막 문제 이후로는 결과 화면으로 이동한 후
3. 점수를 바탕으로 화면을 그리기

삼항연산자를 통해
(마지막 문제인지 아닌지) ? 퀴즈 화면 : 결과 화면
을 출력해 줄 겁니다!

현재 질문/대답의 순서를 담고 있는 변수 questionIndex 와 총 질문/대답의 개수인 questionList.length 를 사용해볼까요?
어떻게 하면 좋을까요? 🤔

questionIndex 보다 questionList.length 가 작으면 아직 퀴즈 중일거에요.
그러다 questionIndex 가 questionList.length 와 같아지면 (3) 에러가 나겠죠!

정답 공개입니다! 😲

body: (questionIndex < questionList.length)
  ? QuizScreen(
    answerPressed: answerPressed,
    questionIndex: questionIndex,
    )
  : ResultScreen(),

이렇게 한 번 써보세요!

첫 번째 질문에서는 ( 0 < 3 ) 이니까 QuizScreen 을,
두 번째 질문에서도 ( 1 < 3 ) 이니까 QuizScreen 을,
세 번째 질문에서도 ( 2 < 3 ) 이니까 QuizScreen 을,
그러다 세 번째에서 버튼을 누르면 (3 < 3) 이 거짓이라 ResultScreen 을 보여줄거에요. 🤩

answerPressed() 함수 안에 setState(); 가 있어서 "화면을 다시 그려!" 라고 따로 말 안해줘도 될거구요.

Run 하고 우리의 예상대로 동작하는지 볼까요?

오! 비록 결과 화면이 엉성하지만 작동을 잘 하네요!! 🥳
좋아요!! 😎 거의 다 왔습니다.

1. 점수를 저장하고
2. 마지막 문제 이후로는 결과 화면으로 이동한 후
3. 점수를 바탕으로 화면을 그리기

📍 마지막 3번!

자, 이제 우리는 totalScore 을 기준으로 결과값을 출력할거에요.

totalScore 은 main.dart 에 있고, 결과 화면은 result_screen.dart 에 써있겠죠?

ResultScreen 위젯 안에서 totalScore 를 참고하고 싶어요. 어떻게 할까요!
네, 여기서도 named parameter 을 쓸 수 있습니다. 😉

int 형 totalScore 변수를 받아볼까요?

result_screen.dart 에 가서 이렇게 바꿔줍니다.

그러면 또! main.dart 에 오류가 뜰거에요.
마찬가지로 변수를 넣어주면 됩니다. 😌

이렇게요!
저에게는 42번째 줄인 ResultScreen 에 변경이 생겼어요.

좋아요! result_screen.dart 으로 totalScore 을 넘겨주었습니다! 😎

이제 ResultScreen 을 더 꾸며볼게요.

제가 예전에

여러분도 구간을 나눠보고 해당하는 구간의 결과 메세지도 미리 써오면 좋겠죠? 😉

라고 했었는데 혹시 하셨나요? 😂

저는
6, 8, 10 : 당신은 무해하고 귀여운 토끼입니다!
12, 14 : 당신은 신난 강아지입니다!
16, 18 : 당신은 앙칼진 고양이입니다!
20, 22, 24 : 당신은 맹렬한 호랑이입니다!
이렇게 썼습니다.

이제 totalScore 을 기준으로 출력하는 Text() 의 값을 나눠줘야 해요!

저는 어떤 식으로 할거냐면요, 결과값을 받는 String 형 변수인 resultMessage 를 만들고 조건문을 이용해서 그 resultMessage 에 미리 정해진 문장을 넣어줄겁니다. 😎

여러분도 이렇게 해보세요!

위치는 여기입니다. 제 result_screen.dart 기준으로 12번째 줄 build 아래에 넣어주었어요!

totalScore, resultMessage, if else 를 사용해서 각자의 멘트를 넣어주세요.

저는 이렇게 조건을 달아주었습니다!

이제 화면 출력을 위해 Text() 위젯 안에 있는 "result page" 대신에 우리가 정의해준 String 변수인 resultMessage 를 넣어주겠습니다.

제 화면 기준 29번째 줄이 바뀌었어요!

보기 좋게 앞뒤로 SizedBox() 도 넣은 것 보이시나요? 🤓

다시 Run 해봅시다!!

여러분도 저처럼 원하는 메세지가 잘 출력되나요? 😇

몇 번 해보니 각각 다 다른 결과 메세지가 출력됩니다!!

좋아요, 맘에 들어요! 🥳


이렇게 해서 3번까지 마무리가 되었어요.

1. 점수를 저장하고
2. 마지막 문제 이후로는 결과 화면으로 이동한 후
3. 점수를 바탕으로 화면을 그리기

근데.. 뭔가 좀 허전하죠? 😞

go back 버튼도 없고 사진도 없고..
다시 시작하려면 다시 Run 을 눌러야 하는 번거로움이 있어요.

자 그럼 문제! 이 퀴즈 앱을 다시 시작하려면 어떻게 해줘야 할까요?
더 쉽게는, 뭘 초기화 해줘야 할까요?

(두구두구)

아마 맞추셨을거 같아요!
questionIndex 와 totalScore 을 모두 0으로 바꿔줘야 합니다!! 👏

그래야 다시 첫 번째 질문을 보여줄거고, 다시 0부터 점수를 더해주겠죠?

그 함수를 빨리 구현해줄게요!


main.dart 에 가서 answerPressed() 함수 아래에 resetQuiz() 라고 void 함수를 만들어봅시다!

그리고 questionIndex 와 totalScore 을 0으로 바꿔줄게요.

👀 잊지 말기!! 변수가 바뀜에 따라 화면을 다시 그리고 싶다면?
setState(); 를 넣어줘야 합니다!

자, 이 세개를 한 번에 다 하면 이렇게 될거에요.

  void resetQuiz() {
    setState(() {
      questionIndex = 0;
    });
    totalScore = 0;
  }

totalScore 은 화면에 영향을 미치지 않으니 역시 setState 안에 넣지 않아도 됩니다 😉

이제 이 함수를 ResultScreen 에게 넘겨줘야 해요.
어떻게요? 바로 named parameter 을 통해서요!

result_screen.dart 로 이동해서 만들어줍시다.

제 화면 기준으로 11번과 7번째 줄에 추가해줬어요!
조금씩 익숙해져 가나요?

이 함수를 go back 버튼에 넣어줄게요!

ElevatedButton(
  onPressed: () => resetQuiz(),
  child: Text("reset quiz"),
),

음, 근데 이름을 통일하고 싶어요. 전 reset quiz 로 버튼 이름을 바꿨습니다!

이제 main.dart 로 넘어가서 함수를 넘겨줍시다!

제 화면에서는 51번 줄이에요!
(괄호) 없이 함수 이름만 넘겨줬습니다.

이제 결과 화면에서 "리셋" 버튼을 누르면 점수와 순서가 초기화 되어서 다시 퀴즈 화면으로 이동할거에요.

한 번 다시 Run 해보죠!

결과 화면 뿐만 아니라 다시하기도 잘 작동 하네요!! 🥳

정말 수고 많으셨습니다. 👏


아직 미적으로는 부족한 부분들이 많지만, 일단 여기서 대략적인 진도는 마무리 할게요.
글씨를 키우거나 사진을 넣는 부분은 여기(쓰는 중)를 참고해주시면 감사하겠습니다 😌

이 짧은 앱 하나 만드는 데도 이렇게 오래 걸리다니.. 막막하실 수도 있어요.
그러나!
익숙해지면 이정도는 금방 만드실거에요.
매개변수를 넣는 과정, 처음엔 이해도 안되고 뭔 소린지 모르다가도
두번 세번 네번 하다보니 점점 더 익숙해지지 않나요?

몰라도 계속 따라하시면 어느 순간 이해가 됩니다.
정말이에요! 제가 그랬으니까요.

여기까지 오시느라 정말 수고 많으셨습니다. 🥰
나만의 퀴즈 앱 만들기는 이 글로 마무리 하겠습니다!

감사합니다.


main.dart

import 'package:flutter/material.dart';
import 'package:personal_quiz/question_list.dart';
import 'package:personal_quiz/screens/quiz_screen.dart';
import 'package:personal_quiz/screens/result_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;
  int totalScore = 0;

  void answerPressed(int score) {
    setState(() {
      questionIndex++;
    });
    totalScore += score;
    print(totalScore);
  }

  void resetQuiz() {
    setState(() {
      questionIndex = 0;
    });
    totalScore = 0;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("나만의 퀴즈 만들기"),
      ),
      body: (questionIndex < questionList.length)
          ? QuizScreen(
              answerPressed: answerPressed,
              questionIndex: questionIndex,
            )
          : ResultScreen(
              totalScore: totalScore,
              resetQuiz: resetQuiz,
            ),
    );
  }
}

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(
                questionList[questionIndex]["answers"][0]["score"]),
            child: Text(questionList[questionIndex]["answers"][0]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(
                questionList[questionIndex]["answers"][1]["score"]),
            child: Text(questionList[questionIndex]["answers"][1]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(
                questionList[questionIndex]["answers"][2]["score"]),
            child: Text(questionList[questionIndex]["answers"][2]["text"]),
          ),
          ElevatedButton(
            onPressed: () => answerPressed(
                questionList[questionIndex]["answers"][3]["score"]),
            child: Text(questionList[questionIndex]["answers"][3]["text"]),
          ),
        ],
      ),
    );
  }
}

result_screen.dart

import 'package:flutter/material.dart';

class ResultScreen extends StatelessWidget {
  const ResultScreen({
    Key? key,
    required this.totalScore,
    required this.resetQuiz,
  }) : super(key: key);

  final int totalScore;
  final Function resetQuiz;

  
  Widget build(BuildContext context) {
    String resultMessage;

    if (totalScore <= 10) {
      resultMessage = '당신은 무해하고 귀여운 토끼입니다!';
    } else if (totalScore <= 14) {
      resultMessage = '당신은 신난 강아지입니다!';
    } else if (totalScore <= 18) {
      resultMessage = '당신은 앙칼진 고양이입니다!';
    } else {
      resultMessage = '당신은 맹렬한 호랑이입니다!';
    }

    return Center(
      child: Column(
        children: [
          SizedBox(height: 150,),
          Text(resultMessage),
          SizedBox(height: 100,),
          ElevatedButton(
            onPressed: () => resetQuiz(),
            child: Text("reset quiz"),
          ),
        ],
      ),
    );
  }
}

question_list.dart

List<Map<String, dynamic>> questionList = [
  {
    'questionText': '평화로운 휴일!\n간만에 찾은 자유시간,\n당신은 무엇을 하시겠습니까?',
    'answers': [
      {'text': '나에게 휴식이란 없다. 소맥 달려~!', 'score': 8},
      {'text': '운동은 못참지! 헬스장 가기', 'score': 6},
      {'text': '노래 들으며 산책이나 할까?', 'score': 4},
      {'text': '피곤해.. 누워서 넷플 정주행하기', 'score': 2},
    ],
  },
  {
    'questionText': '카페에서 이상형을 본 당신!\n무엇을 하시겠습니까?',
    'answers': [
      {'text': '말은 부끄러워서 못 걸거 같다. 눈에만 담기', 'score': 2},
      {'text': '가서 말을 걸어본다. 혹시 애인 있으세요?', 'score': 8},
      {'text': '나 오늘 .. 상태 괜찮나? 일단 거울 보기', 'score': 4},
      {'text': '그 사람 앞에 쪽지를 남기고 떠난다.', 'score': 6},
    ],
  },
  {
    'questionText': '안 친한 친구에게 받은 카톡.\n너 이런 점은 고쳤으면 좋겠어.',
    'answers': [
      {'text': '알겠어 근데 내 일은 내가 알아서 할게..^^', 'score': 4},
      {'text': '이렇게 생각할 수도 있군. 고쳐본다고 한다.', 'score': 2},
      {'text': '어쩌라는거지? 참견 말라고 한 소리 한다.', 'score': 8},
      {'text': '굳이 얼굴 붉히기 싫다. 읽씹한다.', 'score': 6},
    ],
  },
];
profile
개발자로서 100가지 일을 해보고 싶은 조경현의 개발 블로그

1개의 댓글

comment-user-thumbnail
2023년 2월 28일

다 읽었음!!!!

답글 달기