Tech Trail

[Flutter로 간단한 퀴즈앱 만들기 02] 모델 구현 및 화면 전환 (플러터 프론드엔드 반응형 앱) 본문

Project/[Flutter & Django] 퀴즈 앱 만들기

[Flutter로 간단한 퀴즈앱 만들기 02] 모델 구현 및 화면 전환 (플러터 프론드엔드 반응형 앱)

_밍지_ 2023. 12. 21. 11:05
728x90
반응형
SMALL

이전 게시글을 보지 않으신 분들은 아래 링크로 이동하여 먼저 보시는 걸 추천드립니다 :)

 

[Flutter로 간단한 퀴즈앱 만들기 01] 플러터 개발환경 세팅하기 (VS code extensions: Dart / Flutter) (tistory.com)

 

[Flutter로 간단한 퀴즈앱 만들기 01] 플러터 개발환경 세팅하기 (VS code extensions: Dart / Flutter)

안녕하세요! 오늘은 Flutter를 사용하여 간단한 퀴즈 앱을 만들어보겠습니다. Flutter 및 Dart 설치 우선, Visual Studio Code(또는 원하는 다른 편집기)에 Flutter와 Dart를 설치해야 합니다. Visual Studio Code의 E

techtrail.tistory.com

 

저번에 만든 홈화면에 이것저것 추가한 모습입니다.

 

 

 

 

현재까지 screen_home.dart 파일의 코드는 아래와 같고요! 참고하세요.

 

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    Size screenSize = MediaQuery.of(context).size;
    double width = screenSize.width;
    double height = screenSize.height;

    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: Text('My Quiz APP'),
          backgroundColor: Colors.deepPurple,
          leading: Container(),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Center(
              child: Image.asset(
                'images/quiz.jpeg',
                width: width * 0.8,
              ),
            ),
            SizedBox(height: width * 0.024),
            Text(
              '플러터 퀴즈 앱',
              style: TextStyle(
                fontSize: width * 0.065,
                fontWeight: FontWeight.bold,
              ),
            ),
            Text(
              '퀴즈를 풀기 전 안내사항입니다.\n꼼꼼히 읽고 퀴즈 풀기를 눌러주세요.',
              textAlign: TextAlign.center,
            ),
            SizedBox(height: width * 0.048),
            _buildStep(width, '1. 랜덤으로 나오는 퀴즈 3개를 풀어보세요.'),
            _buildStep(width, '2. 문제를 잘 읽고 정답을 고른 뒤\n다음 문제 버튼을 눌러주세요.'),
            _buildStep(width, '3. 만점을 향해 도전해보세요!'),
            SizedBox(height: width * 0.048),
            Container(
              padding: EdgeInsets.only(bottom: width * 0.036),
              child: Center(
                child: ButtonTheme(
                  minWidth: width * 0.8,
                  height: height * 0.05,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: ElevatedButton(
                    // Change here
                    child: Text(
                      '지금 퀴즈 풀기',
                      style: TextStyle(color: Colors.white),
                    ),
                    style: ElevatedButton.styleFrom(
                      primary: Colors.deepPurple,
                    ),
                    onPressed: () {},
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStep(double width, String title) {
    return Container(
      padding: EdgeInsets.fromLTRB(
        width * 0.048,
        width * 0.024,
        width * 0.048,
        width * 0.024,
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Icon(
            Icons.check_box,
            size: width * 0.04,
          ),
          SizedBox(width: width * 0.024),
          Expanded(
            child: Text(
              title,
              style: TextStyle(fontSize: width * 0.04),
            ),
          ),
        ],
      ),
    );
  }
}

 

 

이제 이번 게시글 찐 본론으로 들어가 보자면,

오늘은 퀴즈 모델을 만들 겁니다!

 

퀴즈 모델 생성

 

 

 

 

model 폴더에 model_quiz.dart 파일을 생성하세요. 그 후, 아래와 같이 Quiz 클래스를 정의합니다.

 

class Quiz {
  String title;
  List<String> candidates;
  int answer;

 

 

Quiz.fromMap() 함수를 만들어주면 퀴즈 모델은 완성됩니다.

 

Quiz({this.title, this.candidates, this.answer});

  Quiz.fromMap(Map<String, dynamic> map)
      : title = map['title'],
        candidates = map['candidates'],
        answer = map['answer'];

 

 

 

홈 화면에서 퀴즈 더미 데이터 생성

HomeScreen으로 돌아가서 퀴즈 더미 데이터를 작성합니다.

퀴즈 API를 언제 호출하느냐에 따라 앱의 구조를 다르게 작성할 수 있습니다.

 

1) 앱이 실행되었을 때 퀴즈를 가져오는 방식

2) 퀴즈 풀기 버튼을 눌렀을 때 가져오는 방식

 

저는 "퀴즈 풀기" 버튼을 눌렀을 때 랜덤한 퀴즈들을 가져오는 방식으로 작성하겠습니다.

 

HomeScreen에서는 Quiz를 자동으로 import하고 List<Quiz> quizs를 생성합니다.

그 안에는 Quiz.fromMap을 활용해 더미 데이터를 만듭니다.

 

List<Quiz> quizs = [
    Quiz.fromMap({
      'title': 'test',
      'candidates': ['a', 'b', 'c', 'd'],
      'answer': 0
    }),
    Quiz.fromMap({
      'title': 'test',
      'candidates': ['a', 'b', 'c', 'd'],
      'answer': 0
    }),
    Quiz.fromMap({
      'title': 'test',
      'candidates': ['a', 'b', 'c', 'd'],
      'answer': 0
    }),
  ];

 

 

세 개의 퀴즈를 생성하면 퀴즈 더미 데이터가 완성됩니다.

 

QuizScreen으로 퀴즈 데이터 전달

이제 퀴즈 더미 데이터를 QuizScreen으로 넘겨주어야 합니다.

QuizScreen 클래스를 만들어 screen 폴더 안에 screen_quiz.dart 파일을 생성하세요.

 

 

 

 

Stateful 위젯으로 만들어 내부에 List<Quiz> quizs를 선언합니다.

생성자를 통해 이전 화면으로부터 퀴즈 데이터를 받아오고, 상태 관리를 위한 세 가지 상태 정보를 선언합니다.

 

import 'package:flutter/material.dart';
import 'package:quiz_app_test/model/model_quiz.dart';

class QuizScreen extends StatefulWidget {
  List<Quiz> quizs;
  QuizScreen({this.quizs});
  @override
  _QuizScreenState createState() => _QuizScreenState();
}

class _QuizScreenState extends State<QuizScreen> {
  @override
  Widget build(BuildContext context) {}
}

 

 

첫 번째는 각 퀴즈 별 사용자의 정답을 담아 놓을 _answers 리스트로, 초기 값은 -1로 하겠습니다. (3문제니까 3개)

두 번째로 _answerState는 퀴즈 하나에 대하여 각 선택지가

선택되었는지를 bool 형태로 기록하는 리스트로, 초기값은 false로 하겠습니다. (보기가 4개니까 4개)

마지막은 현재 어떤 문제를 보고 있는지에 대한 _currentIndex입니다.

 

 

  List<int> _answers = [-1, -1, -1];
  List<bool> _answerState = [false, false, false, false];
  int _currentIndex = 0;

 

 

그리고 마찬가지로 MediaQuery로 사이즈 정보를 가져옵니다.

SafeArea 안에 Scaffold를 넣는 방식으로 앞서 구현한 화면과 유사하게 작성하면 됩니다.

 

  @override
  Widget build(BuildContext context) {
    Size screenSize = MediaQuery.of(context).size;
    double width = screenSize.width;
    double height = screenSize.height;
    return SafeArea(
      child: Scaffold(
        backgroundColor: Colors.deepPurple,
        body: Center(
          child: Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(20),
              border: Border.all(color: Colors.deepPurple),
            ),
            width: width * 0.85,
            height: height * 0.5,
          ),
        ),
      ),
    );
  }

 

 

QuizScreen을 "지금 퀴즈 풀기" 버튼을 띄우는 작업을 진행하겠습니다.

실행하기 위해 HomeScreen으로 돌아가 "지금 퀴즈 풀기" 버튼의 onPressed 부분을 채워주세요.

 

 

onPressed: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => QuizScreen(
                            quizs: quizs,
                          ),
                        ),
                      );
                    },

 

 

The method 'QuizScreen' isn't defined for the type '_HomeScreenState'.
Try correcting the name to the name of an existing method, or defining a method named 'QuizScreen'.

 

아 에러..

'_HomeScreenState' 클래스에서 'QuizScreen'이라는 메서드를 찾을 수 없다고 나와 있습니다.

여기서 발생한 에러를 해결하기 위해서는 HomeScreen 파일에서 QuizScreen을 사용할 때 import 문을 추가해야 합니다.

 

import 'package:quiz_app_test/screen/screen_quiz.dart';

 

 

위 import 문을 screen_home.dart 파일 가장 윗 줄에 추가하면 됩니다.

이제 "퀴즈 풀기 버튼"을 누르면 전체가 보라색인 화면이 나온 것을 확인할 수..

 

 

 

없습니다.. 하..

 

 

Dart 언어의 업데이트로 인한 변경사항과 null 안전성 관련된 내용 때문에 발생한 오류입니다.

진짜 이놈의 null safety 진짜 개빡치네요.

Dart 2.12 버전 이후로는 기본적으로 null 안전성(null safety)이 활성화되어, null을 허용하는지 여부를 명시적으로 지정해야 하는데, 이로 인해 코드에서 몇 가지 수정을 하겠습니다.

  1. 기본값 및 Nullable 타입 처리: Dart에서는 기본적으로 매개변수가 null을 허용합니다. Dart 2.12 버전 이후로는 이를 방지하기 위해 required 키워드를 사용하거나 기본값을 지정하는 것이 권장됩니다. 기존 코드에서는 이에 대한 처리가 없어서 발생한 오류입니다.
  2. null 확인 연산자 (??): Dart에서 null이 될 수 있는 변수에 접근할 때는 null 확인 연산자인 ??를 사용하여 기본값을 지정하는 것이 좋습니다. 이를 통해 null일 경우 기본값을 사용할 수 있게 됩니다.

model_quiz.dart 파일 코드를 아래와 같이 변경할게요.

 

class Quiz {
  String title;
  List<String> candidates;
  int answer;

  Quiz({
    required this.title,
    required this.candidates,
    required this.answer,
  });

  Quiz.fromMap(Map<String, dynamic> map)
      : title = map['title'] ?? '',
        candidates = List<String>.from(map['candidates'] ?? []),
        answer = map['answer'] ?? 0;
}

 

 

 

 

드디어 "퀴즈 풀기 버튼"을 누르면 전체가 보라색인 화면이 나왔습니다!

QuizScreen 구현

이제 QuizScreen의 내용을 채워보겠습니다.

옆으로 자연스럽게 넘어가는 퀴즈 화면을 만들 것인데 이를 위해 flutter_swiper 패키지가 필요합니다.

따라서 pubspec.yaml을 열어 flutter_swiper를 입력하고 저장하여 설치합니다.

 

 

최신 버전을 확인하고 채워넣으세요

 

 

설치가 다 되었으면 다시 QuizScreen으로 돌아와 Swiper를 선언하고 physicsNeverScrollable 옵션을 넣습니다. 이 옵션을 적용해 주면 Swipe 모션을 통해 넘어가지 않습니다. 따라서 퀴즈를 스킵할 수 없도록 됩니다.

 

child: Swiper(physics: NeverScrollableScrollPhysics()

 

 

그리고 loopfalse, itemCount를 넣습니다.

itemBuilder는 새로운 함수를 작성하여 처리하겠습니다.

 

 

... 그전에...

 

flutter pub get 명령을 실행했던 게 패키지 설치가 안 되어있네요...

 

 

 

dart 언어 진짜.. null safety 때문에 미치겠어요.

에러 해결하고 올게요. 다음 게시글에서 만나요...

728x90
반응형
LIST