Front-End/Flutter

2. <Quiz APP>을 통한 Flutter Tutorial / Render Content Conditionally, Lifting State Up

sd4beatles 2024. 11. 17. 00:04

1. Introuction

현재까지 너무 많은 widget code가 main에 '쏠려'있는 것을 느꼈을 것이다. 그래서, 이번 lesson에선 추가적인 dart file을 만들어서 가독성을 높히는 방법을 찾아보도록 하자.

-lib
|-main.dart
|-start_screen.dart
|-question_screen.dart
|-quiz.dart

2. Quiz.dart

main에 MaterialApp을 담을 수 있는 class를 만들어보자. 우리는 Quiz라는 widget class를 만들려고 하며, 이 두개의 class는 아래와 같이 작성될 수 있다.

import 'package:adv_basics/question_screen.dart';
import 'package:flutter/material.dart';
import 'package:adv_basics/start_screen.dart';

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

  @override
  State<Quiz> createState(){
    return _QuizState();
  }
}

class _QuizState extends State<Quiz>{

  @override
  Widget build(context){
    return MaterialApp(
    home: Scaffold(
      body:Container(
        decoration:BoxDecoration(
          gradient:LinearGradient(
            colors:[
              Color.fromARGB(255, 89, 4, 138),
              Color.fromARGB(255, 107, 15, 168)],
            begin:Alignment.topLeft,
            end:Alignment.bottomRight,
            ),

        ),
        child: screenWidget,
    ))
    );
  }
}

✍️Note

  1. _QuizState class는 Quiz class의 파생 된 private class를 이다. build 함수를 오버로딩하여, main에서 지정한 MaterialApp을 모두 이전시킨다.
  2. Quiz class에서는 꼭 createState 함수를 정의 내린 뒤, 두번째 정의한 class를 return 한다.

3. question_screen.dart

question_screen.dart는 아래와 같은 형태로 임시적으로 만들고 싶다고 가정하자. (추후에 더 상세하게 수정할 예정임)

위 그림처럼 규격을 갖추기 위해선,Quiz.dart와 같은 StatefulWidget class를 선언할 필요가 있다.



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"))

    ],)
    );
  }
}

4. Render Content Conditionally, Lifting State Up

import 'package:flutter/material.dart';


//Center enables the whole widget to fill the screen
class StartScreen extends StatelessWidget{
  const StartScreen(this.startQuiz,{super.key});

  @override
  Widget build(context){
    return  Center(
      child:Column(
          //생략 
          //child 4
          const SizedBox(height:30),
          //the button to activate function
          //<point1> 여기를 수정할 것!
          OutlinedButton.icon(onPressed:StartScreen(), 
          //chiild is no loger used uner QutilineButton.icon; use label
          label: const Text("Start Quiz"),
          icon:const Icon(Icons.arrow_circle_right),
          style:OutlinedButton.styleFrom(
            foregroundColor:Colors.white,
            ),
          )
        ],

      )
    );
  }
}

quiz.dart를 보면, 우리가 처음으로 실행할 Widget은 StartScreen()이라는 것을 알 수 있다. 그러나, 우리가 button을 누르고 나면,wideget은 우리가 막 설정한 위 두 class widget으로 이동을 해야한다. 여기서 핵심은 초기화면 이후 다른 화면으로 전환되는 하나의 장치가 필요하다는 점이다.

4.1 Store switching widgets to a Temporary Widget

class _QuizState extends State<Quiz>{

  var activeScreen="start-screen";

  //a function for setting up state
  void switchScreen(){
    //anonymous function to swatich from StartScreen to QuestionScreen
    setState(() {
      activeScreen="questions-screen";
    });
  }

우리는 먼저 switchScreen이라는 void 함수를 만든다. 이 함수는 이름 자체에서 알 수 있듯이, 한 state에서 다른 state로 전환을 위한 도구로 작동한다. 그리고 그 함수 안에, State class에서 제공되는 setState를 이용해서 무익명 함수를 지정하고, activeScreen 변수가 자율적으로 변화되도록 허용한다.

switchScreen은 argument을 아무것도 받지 않는, void function임을 상기하자. 이 때, 이 switchScreen 함수는 StartScreen의 argument로 들어가게 된다. 그리고 현재, StartScreen 객체를 받아줄 widget 변수가 필요한데, 이를 screenWidget이라 칭하자.

class _QuizState extends State<Quiz>{

  var activeScreen="start-screen";

  //a function for setting up state
  void switchScreen(){
    //anonymous function to swatich from StartScreen to QuestionScreen
    setState(() {
      activeScreen="questions-screen";

      if (activeScreen=="questions-screen"){
       screenWidget=const QuestionScreen();
    }


    });
  }

  @override
  Widget build(context){

    // widget variable stroing screenWidget class
    Widget screenWidget=StartScreen(switchScreen);

즉, QuizState 를 통해서 StartScreen을 호출하고, Start Screen은 switchScreen을 매개변수로 가지기 때문에,swtichcase가 호축된다.

이러한 방식으로 screen의 전환을 도모할 수 있다. 다만, 현재까지, StartScreen class는 아무런 매개변수를 가지고 있지 않도록 작성되었다. 이를 변경하기 위해선, 우리는 StartScreen의 constructor에 this.startQuiz(명칭은 아무렇게 바꿀 수 있음) 매개변수로 받을 수 있도록 허용하고, build method 아래에다가 startQuiz(void 함수)를 초기화시킨다.

//start_screen.dart


class StartScreen extends StatelessWidget{
  const StartScreen(this.startQuiz,{super.key});
  final void Function() startQuiz;

마지막으로, STartScreen class의 OutlinedButton.ico의 onPressed를 startQuiz로 변경한다.
아래는 fullcode이다.


import 'package:flutter/material.dart';


//Center enables the whole widget to fill the screen

class StartScreen extends StatelessWidget{
  const StartScreen(this.startQuiz,{super.key});
  final void Function() startQuiz;

  @override
  Widget build(context){
    return  Center(
      child:Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          //child 0

          //child 1
          Opacity(opacity:0.6,
            child:Image.asset(
              "assets/images/quiz-logo.png",
              width:300,
              color: Color.fromARGB(221, 230, 211, 241),

            )
            ),
          //chidl 2
          const SizedBox(height:80),
          //child 3
          const Text(
            "Learn Flutter the fun way!",
            style:TextStyle(
              color:Colors.white,
              fontSize:24),
          ),
          //child 4
          const SizedBox(height:30),
          //the button to activate function
          OutlinedButton.icon(onPressed:startQuiz, 
          //chiild is no loger used uner QutilineButton.icon; use label
          label: const Text("Start Quiz"),
          icon:const Icon(Icons.arrow_circle_right),
          style:OutlinedButton.styleFrom(
            foregroundColor:Colors.white,
            ),
          )
        ],

      )
    );
  }
}