[Flutter] Dart 언어와 StatefulWidget
다트 언어는 자바처럼 객체지향 언어이자 Type-Safe 언어이다.
런타임 중에 타입 안정성을 확인하지만 타입 추론을 지정하기에 해당 변수가 처음 할당될 때 타입을 추론한다.
자바는 타입이 객체와 Primitive Type으로 나뉘지만, 다트는 모든 타입을 객체로 다룬다.
자바와 마찬가지로 다트도 모든 객체의 조상인 Object가 존재하고, int 같이 자바에서는 Primitive Type으로 다루던 타입도 객체로 다루기에 null로 입력될 수 있다.
Scaffold 위젯을 생성할 때 파라미터로 몇 가지를 넘겨야 하는데, 위의 예시를 보면 Color? 을 파라미터로 넘긴다고 명시되어 있다.
Color? 에서 Color는 타입이고, ? 는 null이 들어갈 수 있다는 의미이다.
backgroundColor 값을 설정하지 않으면 null로 설정한 것과 같이 설정되니 생략해도 상관없다.
var variable;
variable = "str";
String? variable; // non-nullable
String variable; // nullable
변수를 선언하고 나중에 값을 할당하는 경우 다트는 해당 타입을 dynamic 타입으로 설정한다.
하지만 이렇게 작성하면 오류가 발생하기 쉬우니 Type-Safe 한 방법을 사용해야 한다.
String varibale; 처럼 문법으로 특정 타입으로 변수를 선언함을 명시해두자.
void main() {
runApp(
MaterialApp(
home: Scaffold(
backgroundColor: Colors.deepOrange,
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.purple, Colors.blue
],
begin: Alignment.topLeft,
end: Alignment.bottomLeft,
),
),
child: const Center(
child: Text("Hello World!", style: TextStyle(
color: Colors.yellow,
fontSize: 28,
),),
),
),
),
),
);
}
플러터로 화면을 만들다 보면 위젯들이 중첩돼서 사용되고, 소스코드가 길어져 한 눈에 보기 힘들어진다.
이 때 공통으로 사용되는 요소를 분리할 수 있다.
앞서 말했듯 다트는 모든 것을 객체로 다룬다.
로드하는 위젯도 객체고, 자바와 같이 객체를 만들기 위해서는 클래스를 정의한다.
void main() {
runApp(
const MaterialApp(
home: Scaffold(
backgroundColor: Colors.deepOrange,
body: GradientContainer(),
),
),
);
}
class GradientContainer extends StatelessWidget {
const GradientContainer({super.key});
@override // meta data
Widget build(context) {
// TODO: implement build
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.purple, Colors.blue
],
begin: Alignment.topLeft,
end: Alignment.bottomLeft,
),
),
child: const Center(
child: Text("Hello World!", style: TextStyle(
color: Colors.yellow,
fontSize: 28,
),),
),
);
}
}
Center 부분을 class로 분리해서 사용하고 있는 예시이다.
추상클래스인 StatelessWidget 클래스를 상속받고 추상메서드로 정의된 build 메서드를 오버라이드한다.
위젯을 다룰 때 const 키워드를 사용했는데, 다트에서는 메모리를 최적화하기 위해 const 키워드를 사용한다.
플러터는 기본적으로 리액티브 프로그래밍 모델을 따르고, 데이터가 변경될 때 UI를 업데이트 하는 대신 새로운 데이터를 받아들이고 이에 따라 UI를 업데이트한다. (사용자의 입력으로 애플리케이션 상태가 변할 때 업데이트)
성능을 위젯의 인스턴스가 이전의 프레임과 동일한지 확인하고 동일하다면 위젯 트리를 다시 그리지 않는다.
const 키워드가 붙은 위젯은 값이 변하지 않는 상수인 위젯임을 의미하고 이는 다시 그릴 필요가 없음을 의미한다.
StatefulWidget은 시간이 지남에 따라 인스턴스가 바뀔 수 있으니 const 키워드를 붙이면 안되고,
StatelessWidget은 변경될 일이 없으니 const 키워드를 붙여 한 번만 생성하고 재활용하도록 설계할 수 있다.
여기서 State는 위젯 내부의 정보를 의미하고, 상태가 바뀔 때 마다 UI가 다시 렌더링된다.
하나의 위젯에서 바뀌는 부분과 바뀌지 않는 부분이 함께 존재하고 있는 경우 두 부분으로 나눠 StatefulWidget과 StatelessWidget으로 작성하는 편이 메모리 사용에 있어서 합리적이다.
const 변수는 컴파일 시점에서 초기화되고, final 변수는 런타임 시점에서 초기화된다.
var a = 1;
a = 2; // 새로운 주소를 만들어서 할당.
const a = [1,2];
a.add(3); // 오류
final a = [1,2];
a.add(3); // 정상
var로 변수를 선언한 경우 변수를 재할당 할 수 있고, 변수가 이전에 참조하던 주소는 다트의 가비지 컬렉터가 처리해준다.
위의 예시로 const와 final의 차이를 살펴보자..
플러터의 위젯에서 생성자는 보통 위젯의 속성을 초기화 할 때 사용한다.
위의 예시에서는 새로 만든 위젯인 GradientContainer 위젯이 key 매개변수를 선택적으로 받고 부모 클래스인 StatelessWidget 의 생성자를 호출한다.
const GradientContainer({Key key}) : super(key: key);
(위와 같이 쓸 수도 있다)
생성자를 생성하지 않으면 컴파일러가 부모 클래스의 빈 생성자를 호출하는 생성자를 만들어 호출하게 된다.
위와 같은 방식으로 특정 부분을 위젯으로 만들어서 재사용 할 수 있다.
생성자에서 {} 구문을 사용하면 선택적 매개변수로 인식돼 생성자를 호출할 때 해당 매개변수를 생략할 수 있다.
{ } 구문으로 선택적 매개변수를 구현할 때도 인자 앞에 required 키워드를 붙여서 해당 인자를 강제할 수도 있다.
class StyledText extends StatelessWidget {
const StyledText(this.text, {Key? key}) : super(key: key);
const StyleText.constructorName({super.key}) : ...
final String text;
}
StyledText.constructorName();
위의 예시에서 StyledText 객체를 생성할 때 매개변수 하나는 무조건 받게 된다.
매개변수로 넘겨받은 text는 메서드의 선언부에서 따로 지정해주지 않아도 클래스의 필드 변수에 할당된다.
자바와는 다르게 다트에서 생성자를 정의할 때는 선언부를 필수로 작성하지 않아도 된다.
C++ 처럼 생성자에서 초기화 리스트 부분과 생성자의 본문 부분을 따로 지정할 수 있다.
자바는 생성자 오버로딩을 지원하지만, 다트는 이름 있는 생성자를 통해 여러 개의 생성자를 정의할 수 있다.
StatefulWidget을 정의하는 예시를 살펴보자.
class StateWidget extends StatefulWidget {
const StateWidget({super.key});
void doSomething() {
setState(() {
// do...
});
}
@override
State<StateWidget> createState() {
return _StateWidget();
}
}
class _StateWidget extends State<StateWidget> {
@override
Widget build(context) {
return ...;
}
}
StatefulWidget을 정의할 때는 두 개의 클래스가 필요하다.
1. StatefulWidget을 상속받는 클래스
우선 StatefulWidget을 사용한다는 의미는 위젯 내부에서 어떤 상태가 변경될 수 있음을 의미한다.
버튼이 있고 버튼을 누르면 함수가 실행돼 위젯 내부의 상태를 변경한다고 생각해 보자.
이 경우 상태가 변경되고 변경된 상태를 반영하기 위해 UI를 다시 렌더링 해야 한다.
이 때 setState() 메서드를 사용한다.
setState() 메서드 내부에 익명 함수를 추가해 상태를 변경하는 코드를 넣어주면 변경된 상태에 따라 UI가 다시 렌더링된다.
2. State를 상속받는 클래스
StatefulWidget을 상속받는 클래스는 createState() 메서드를 구현하도록 강제하는데, 이 메서드의 반환 타입은 State이다.
클래스 앞에 _ 를 붙이면 접근 제어자가 private로 설정된다.
자바에서의 private와 다트에서의 private는 기능이 좀 다르다.
private로 설정된 클래스는 해당 파일 내부에서만 접근할 수 있다. (import로 가져와도 접근할 수 없음)
State 클래스는 build 메서드를 구현하도록 강제하고, 해당 메서드의 반환값으로 상태가 변경될 수 있는 부분을 넣어둔다.
build 메서드는 현재의 상태에 따라 화면에 표시되는 위젯을 렌더링한다.
위젯의 상태가 변경됐을 때 build 메서드를 실행하는 방식으로 동작한다.
변경이 없는 위젯 자체인 StatefulWidget의 인스턴스는 생성자를 정의할 때 const 키워드를 붙일 수 있고,
StatefulWidget의 인스턴스가 참조하는 State 인스턴스는 변경사항이 발생할 수 있어 StatefulWidget의 createState 메서드를 통해 변경 가능성을 관리하고 생성자를 정의할 때 const 키워드를 붙일 수 없다.
'Mobile > Flutter' 카테고리의 다른 글
[Flutter] Riverpod 상태 관리 (0) | 2023.06.24 |
---|---|
[Flutter] Theme와 세 가지 트리 (0) | 2023.06.19 |
[Flutter] 여러 가지 화면과 사용자 입력 관리 (0) | 2023.06.17 |
[Flutter] 위젯 렌더링과 다트 문법 (0) | 2023.06.16 |
[Flutter] 프로젝트 구조와 Widget (1) | 2023.06.11 |
댓글
이 글 공유하기
다른 글
-
[Flutter] Theme와 세 가지 트리
[Flutter] Theme와 세 가지 트리
2023.06.19 -
[Flutter] 여러 가지 화면과 사용자 입력 관리
[Flutter] 여러 가지 화면과 사용자 입력 관리
2023.06.17 -
[Flutter] 위젯 렌더링과 다트 문법
[Flutter] 위젯 렌더링과 다트 문법
2023.06.16 -
[Flutter] 프로젝트 구조와 Widget
[Flutter] 프로젝트 구조와 Widget
2023.06.11