[Flutter] 위젯 렌더링과 다트 문법
버튼을 눌렀을 때 다른 화면으로 라우팅 기능을 플러터에서 구현해보자.
class Quiz extends StatefulWidget {
const Quiz({super.key});
@override
State<Quiz> createState() {
return _QuizState();
}
}
class _QuizState extends State<Quiz> {
Widget activeScreen = const StartScreen();
void switchScreen() {
activeScreen = const QuestionsScreen();
}
@override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(255, 78, 13, 151),
Color.fromARGB(255, 107, 15, 168),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: activeScreen,
),
),
);
}
}
StatefulWidget을 결정할 때 State를 결정하는 클래스를 사용하자.
State클래스에서 setState 메서드를 호출하면 플러터는 build 메서드를 실행하고 이전 상태와 build 메서드를 실행한 후의 상태를 비교한다.
비교해서 변화가 있다면 위젯을 다시 렌더링하는 방식으로 동작한다.
동적으로 페이지를 변경할 때 이런식으로 변수에 위젯을 담고 setState 메서드를 통해 변수에 담는 위젯을 변경하는 방식으로 구현할 수 있다.
class StartScreen extends StatelessWidget {
const StartScreen(this.startQuiz, {super.key});
final void Function() startQuiz;
}
다트 언어에서 모든 것은 객체로 다뤄지기에 메서드 또한 객체로 다룰 수 있다.
생성자가 함수 객체를 받도록 하고 필드에 함수를 설정해 해당 함수를 생성자로부터 초기화하도록 설정한다.
변수에 함수를 저장할 수 있다는 부분이 자바와 좀 다르다.
class _QuizState extends State<Quiz> {
Widget activeScreen = StartScreen(switchScreen); // 오류
void switchScreen() {
activeScreen = const QuestionsScreen();
}
}
StartScreen 생성자를 호출할 때 매개변수로 함수를 지정할 수 있으니 switchScreen을 매개변수로 지정하면 될 것 같지만..
activeScreen 변수를 초기화 할 때 switchScreen 함수를 사용한다.
클래스의 인스턴스가 생성될 때 변수의 초기화가 메서드의 초기화가 동시에 발생하기에 오류가 발생한다.
다트에서는 객체 생성자가 호출되면서 인스턴스 변수가 초기화되는 과정에서 해당 인스턴스의 메서드를 참조하는걸 허용하지 않는다.
해당 문제를 해결하기 위해 initState 메서드를 오버라이드하자.
class _QuizState extends State<Quiz> {
Widget? activeScreen;
@override
void initState() {
super.initState();
activeScreen = const QuestionsScreen();
}
void switchScreen() {
activeScreen = const QuestionsScreen();
}
initState 메서드는 "객체가 처음 생성될 때" 추가로 수행할 작업을 명시하는 역할을 한다.
(필요한 초기 설정이나 데이터를 로드할 때 사용된다)
activeScreen은 객체가 처음 생성된 바로 직후에 initState 메서드를 통해서 초기화되니 null 값을 가질 수 있도록 Widget? 타입으로 선언하자.
StatefulWidget은 처음 생성될 때 단 한 번만 initState 메서드를 실행해 초기 설정을 진행한다.
처음 생성될 때 build 메서드도 함께 실행되고, build 메서드는 setState 메서드가 호출될 때 마다 다시 실행된다.
위젯이 삭제될 때는 dispose 메서드가 실행된다.
이렇듯 플러터는 위젯의 생명주기를 관리한다.
간단한 기능을 구현하거나 규모가 작은 애플리케이션인 경우 삼항연산자나 if 구문으로 경우에 따라 다른 위젯을 빌드하는 방식으로 구현해도 되지만, 각각의 화면이 복잡해지고 상태 변경에 따른 로직이 복잡한 경우 라우팅을 사용하는 편이 합리적이다.
데이터베이스를 통해서 데이터를 가져오든, 메모리에 있는 데이터를 가져오든 데이터를 어딘가에서 가져온 후 화면을 렌더링하는 작업을 수행해야 한다.
1000개의 데이터로 화면을 렌더링해야 한다. 그럼 1000번 하드코딩 해야 할까?
프로그래밍 언어는 모두 반복문이나 다른 방법을 제공한다.
class _QuestionsScreenState extends State<QuestionsScreen> {
@override
Widget build(context) {
final currentQuestion = questions[0];
return SizedBox(
width: double.infinity, // mainAxisSize... 이거도 ㄱㅊ
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
currentQuestion.text,
style: const TextStyle(
color: Colors.white,
),
),
const SizedBox(
height: 30,
),
...currentQuestion.answers.map((answer) {
return AnswerButton(answerText: answer, onTap: () {});
},),
],
),
);
}
}
예시에서 currentQuestion은 questions 객체에서 가져온 첫 번째 값이다.
currentQuestion.answers는 리스트이고 map 메서드를 사용해 리스트를 순회한다.
map 메서드는 자바의 forEach 문법과 비슷하다고 생각하면 된다.
각 요소에 대해 구현부에 있는 함수를 실행하고 그 결과를 새로운 Iterable에 수집한다.
... 은 다트에서 스프레드 연산자로 불리고 컬렉션의 개별 요소를 분리할 때 사용된다. (Iterable 객체에 사용할 수 있다)
위의 예시에서는 일단 map으로 answers 리스트를 순회해서 리스트의 각 요소에 대해 AnswerButton 위젯을 생성한다.
map 메서드로 반환된 Iterable<AnswerButton> 객체를 스프레드 연산자를 사용해 각각의 AnswerButton 위젯으로 변환한 후 Column에 추가한다.
즉, map은 리스트를 순회할 때 사용하고 스프레드 연산자는 Iterable을 위젯으로 변경할 때 사용한다.
map 메서드는 원본 리스트를 조작하지 않고 값을 읽어오기만 함을 기억하자.
스프레드 연산자를 사용하면 Iterable 객체를 각각의 요소로 변환하고, Iterable 객체를 List로 변환할 때는 .toList() 메서드를 사용하면 된다.
map과 비슷한 기능으로 다트는 where 메서드를 제공한다.
where 메서드로 주어진 조건을 만족하는 요소만 포함하는 새로운 컬렉션을 만들 수 있다.
이렇듯 다트가 제공하는 map과 where 메서드는 자바에서 제공하는 Stream API와 매우 유사하게 작동한다.
추가로, State를 상속받는 클래스에서 위젯에 접근하려면 State 클래스가 제공하는 widget 변수를 통해 접근할 수 있다.
class Circle {
double _radius;
Circle(this._radius);
double get radius {
return _radius;
}
}
void main() {
var myCircle = Circle(5);
print(myCircle.radius);
}
Widget build(BuildContext context) {
final summaryData = getSummaryData();
final numTotalQuestions = questions.length;
final numCorrectQuestions = summaryData.where((data) => data['user_answer'] == data['correct_answer']
).length;
다트가 제공하는 기능에 대해 몇 가지 더 알아보자.
자바로 애플리케이션을 작성할 때 클래스 내부 변수를 private 접근제어자로 설정하고 getter를 통해서 해당 변수에 접근할 수 있는 캡슐화를 자주 사용한다.
다트도 같은 기능을 제공하지만, 문법이 조금 다르다.
위의 예시에서 double get radius { ... } 부분을 보면 알 수 있듯 다트에서는 get과 메서드 이름을 함께 사용해 getter를 구현한다.
메서드처럼 괄호는 없지만 변수 자체를 메서드처럼 활용할 수 있다.
자바에서는 함수형 인터페이스의 구현체로 람다 표현식으로 간단하게 작성할 수 있는데, 다트에서도 비슷한 기능을 제공한다.
다트에서는 화살표 함수라는 이름으로 람다 표현식과 유사한 기능을 제공한다.
함수의 본문이 단일 표현식으로 구성된 경우 { } 와 return 없이 함수를 바로 정의할 수 있다.
'Mobile > Flutter' 카테고리의 다른 글
[Flutter] Riverpod 상태 관리 (0) | 2023.06.24 |
---|---|
[Flutter] Theme와 세 가지 트리 (0) | 2023.06.19 |
[Flutter] 여러 가지 화면과 사용자 입력 관리 (0) | 2023.06.17 |
[Flutter] Dart 언어와 StatefulWidget (0) | 2023.06.14 |
[Flutter] 프로젝트 구조와 Widget (1) | 2023.06.11 |
댓글
이 글 공유하기
다른 글
-
[Flutter] Theme와 세 가지 트리
[Flutter] Theme와 세 가지 트리
2023.06.19 -
[Flutter] 여러 가지 화면과 사용자 입력 관리
[Flutter] 여러 가지 화면과 사용자 입력 관리
2023.06.17 -
[Flutter] Dart 언어와 StatefulWidget
[Flutter] Dart 언어와 StatefulWidget
2023.06.14 -
[Flutter] 프로젝트 구조와 Widget
[Flutter] 프로젝트 구조와 Widget
2023.06.11