Flutter 스터디 18 final 변수와 const 변수, 코드 리팩토링(refactoring)
조금 매운맛 6 final과 const 변수 완벽하게 이해하기
변수는 항상 변할 수 있음 = mutable한 속성을 가진다.
final / const
final 변수 : 단 한번만 설정할 수 있다.
const 변수 : 컴파일 시 상수가 된다.
- final와 const를 모두 modifier 제어자라고 한다.
제어자란 클래스, 변수, 메소드를 정의할 때 함께 쓰여서 옵션을 정해주는 역할
예) 접근제어자 = 클래스, 변수, 메소드에 접근을 제안하는 역할\
- 일단 변수값이 한번 초기화 되면 바꿀수 없음. = immutable
1) finla 변수
초기화 하는 방법 1: 변수 선언시 초기화 (예) 바로 위 그림 '에러 뜸' 참조)
초기화 하는 방법 2: 객체 생성 시 외부 데이터를 받아 생성자를 통해서 초기화
// Dart
class Person{
final int age;
String name;
Person(this.age, this.name); // 생성자
}
void main(){
Person p1 = new Person(21, 'Top'); // 생성자를 통해서 초기값 할당
print(p1.age);
}
> 생성자를 final 변수에 초기값 할당 했으므로 이후에 더이상 age 변수는 변할 수 없음
초기화 방법 2의 다른 예시)
> 한 App에 main.dart / question.dart / answer.dart 가 존재
질문과 답변이 함께 있는 map 형식의 survey 변수가 있음.
main.dart : survey 변수 존재, 사용자가 선택한 질문과 답 출력하는 로직
question.dart : 질문이 담길 question 변수 존재, 질문을 스크린에 보여줌
answer.dart : 답변이 담길 answer 변수 존재, 답을 스크린에 보여줌
> 사용자가 답변을 선택하기 전까진 question.dart의 question 변수와 answer.dart의 answer 변수에는 아무 값도 할당되지 않음.
사용자가 선택을 하는 순간 value 값이 할당되는 동시에 초기화 됨.
이 때 final 변수를 사용.
const 변수와 똑같이 한번 초기화되면 변하지 않지만 '초기화 되는 시점이 app이 실행될 때' = run-time constant
But 사용자가 선택을 1번 하고 다시 다른 질문을 선택하는 경우라면?
> 아예 rebuild 해야함.
build 메소드들이 rebuild 되면서 사용자가 선택한 text 값(변수)도 초기화가 되어 다른 질문으로 rebuild 됨
final response = await http.get('http://jsonplaceholder..');
> app이 실행된 후, 웹상에서 데이터가 전송될때까지 기다렸다가 후에 값 저장
response 변수는 컴파일 시 X, 앱이 실행되고 나서 초기화가 되기 때문에 final 키워드 사용해서 run-time constant로 지정
2) Const 변수
- complie-time constant는 컴파일 시에 상수가 됨
> 컴파일 시에 값이 이미 초기화 되어 있어야 하고 이후에는 변하지 않음
- const 변수는 선언과 동시에 초기화
> DateTime은 실행될때마다 현재시간을 불러오기 때문에 변수가 바뀜.
const 변수에 이 값을 할당할 수 없음.
final 초기화 방법 2 다른 예시의 survey 변수
> survey 변수의 질문과 답은 한번 지정되면 바꿀 필요가 없음
> 이런 경우 const 사용
> 컴파일 시부터 상수화 되어 런타임 시에도 반드시 그 값이 유지되어야 하는 변수에 사용
예) 파이값은 3.141592 . . . 변하지 않기 때문에 const 키워드를 사용해서 컴파일 때부터 상수화 시킴
정리
1. const 변수는 컴파일 시에 상수화
2. final 변수는 런타임 시에 상수화
3. Compile-time constant = Run-time constant
> const 변수로써 컴파일시에 상수화가 된다는 것은 runtime 시에도 value 값이 절대로 변하지 않는 상수로서의 특성을 그대로 유지한다.
4. final 변수는 rebuild 될 수 있음.
> stateless 위젯처럼 immutable 하기 때문에 그 값이 변경되어야 한다면 build 메소드 내에서 rebuild 되어야 한다.
조금 매운맛 7 로그인 페이지 :코드 리팩토링(refactoring)
구현하는 것에 중점을 두는 것이 아니라 효율성 있는 코드 짜기, 리팩토링에 중점
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/glogo.png'),
),
> Opacyty : 투명도
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
> SpaceEvenly는 위젯 내의 요소들을 똑같은 비율로 배치하는 속성
이제까진 sizedbox로 간격을 맞춰줬지만 까다로운 상황에선 귀찮아짐
그래서 이미지를 하나 더 넣어 위젯 내 요소수를 3개로 만들고 SpaceEvenly 속성을 이용하면 버튼 중앙에 text나 그림을 적당하게 배치 시킬수 있다.
>> 즉, 투명도를 설정해서 구글 로고는 보이지 않지만 그림과 텍스트를 적절하게 배치하기 위한 요소
shape: RoundedRectangleBorder( // 버튼을 둥글게
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
> 버튼 각을 둥글에 만드는 속성. radius가 높을 수록 더 둥글어짐.
...
ButtonTheme(
height: 50.0,
child: ElevatedButton(
...
> 버튼 높이를 조절하기 위해서 buttonthem으로 감싸서 설정
//main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login App',
theme: ThemeData(primarySwatch: Colors.grey),
home: LogIn(),
);
}
}
class LogIn extends StatelessWidget {
const LogIn({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text(
'Sign In',
style: TextStyle(color: Colors.white),
),
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/glogo.png'),
Text(
'Login with Google',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/glogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
),
onPressed: () {},
),
shape: RoundedRectangleBorder( // 버튼을 둥글게
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/flogo.png'),
Text(
'Login with Facebook',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/flogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/mlogo.png'),
Text(
'Login with Email',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/mlogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.green,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
],
),
),
);
}
}
> 코드가 너무 길고 재사용성, 효율성을 따졌을 때 0점 코드
리팩토링!!
Firebase: 로그인 화면 디자인
리팩토링에 앞서서 기본적으로 만들어야 할 것
1. 폴더 및 dart 파일 생성
2. 파일 복붙(강좌 이후 버튼과 속성들이 바뀐게 있으므로 본인꺼 사용하면 에러 안남)
//main.dart
import 'package:flutter/material.dart';
import 'login_app/login.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase login app',
home: LogIn(),
);
}
}
//my_button.dart
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Opacity(
opacity: 0.0,
child: Image.asset('images/glogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
),
onPressed: (){},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(0.0),
),
),
);
}
}
// login.dart
import 'package:flutter/material.dart';
class LogIn extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text(
'Sign In',
style: TextStyle(color: Colors.white),
),
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/glogo.png'),
Text(
'Login with Google',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/glogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.white,
),
onPressed: () {},
),
shape: RoundedRectangleBorder( // 버튼을 둥글게
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/flogo.png'),
Text(
'Login with Facebook',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/flogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/mlogo.png'),
Text(
'Login with Email',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/mlogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.green,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
],
),
),
);
}
}
//my_button.dart
//생성자
const MyButton({Key? key, required this.image, required this.text, required this.color, required this.radius, required this.onPressed}) : super(key: key);
//속성들의 변수 지정
final Widget image;
final Widget text;
final Color color;
final double radius;
final VoidCallback onPressed;
초기화 하여 값 전달을 위해서 변수를 만들고 생성자 생성
* 주의
강의에서 처럼 required를 붙이지 않으면 오류가 뜸. 강좌 이후 Null safty 이슈로 문제가 뜨는 것
자세한건 다음 댓글 참조
이후 캡쳐의 빨간줄에 맞추어 다음 같이 변수들로 변경
> final 변수들은 한번 초기화 되면 절대 변하지 않음.
그러나 로그인 페이지에서 새로운 값들이 생성자들을 통해서 전달 되면 stateless 위젯 특성상 build 메소드 내에 모든 위젯들이 리빌드 되는 과정을 통해서 변수들도 리빌드 되어 새로운 값이 적용됨.
login.dart 파일의 body에 많은 코드들이 너무 나열되어 있음.
따로 메소드로 추출해서 분리
1. login.dart 파일의 LogIn 클래스 내 body 위젯 내 Padding 부분 블럭처리
** 주의 Padding 위젯 끝에 , 콤마 앞 ) 괄호까지만!!
2. 우측마우스 - Refactor - Extrac Method 클릭
메소드 이름은 buildButton
3. 생성 완료 후, Widget 타입으로 변경 및 _ 언더바
++ 변수나 메소드 앞에 붙이는 _ 언더바는 접근제어자를 의미
즉, Private 역할과 같음
Dart에서 Private 수준은 라이브러리 수준.
간단히 말하자면 같은 파일 내에서만 접근 가능
= 위의 _buildButton 메소드는 login.dart 안에서만 접근 가능해짐
접근제어자 사용하는 이유 : 외부에서 불필요한 접근을 못하게 만들어 데이터가 임의로 변경되지 않기 위해서ㅏ
Mybutton 클래스를 이용해서첫번째 버튼 아래에 버튼 하나 만들기
//login.dart
MyButton(image: Image.asset('images/glogo.png'),
text: Text(
'Login with Google',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
color: Colors.white,
radius: 4.0,
onPressed: (){}
),
SizedBox(
height: 10.0,
),
전체 코드 및 결과물
//main.dart
import 'package:flutter/material.dart';
import 'login_app/login.dart';
void main(){
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Firebase login app',
home: LogIn(),
);
}
}
//my_button.dart
import 'package:flutter/material.dart';
class MyButton extends StatelessWidget {
//생성자
const MyButton({Key? key, required this.image, required this.text, required this.color, required this.radius, required this.onPressed}) : super(key: key);
//속성들의 변수 지정
final Widget image;
final Widget text;
final Color color;
final double radius;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
image,
text,
Opacity(
opacity: 0.0,
child: Image.asset('images/glogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: color,
),
onPressed: onPressed,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(radius),
),
),
);
}
}
//login.dart
import 'package:flutter/material.dart';
import 'package:log_in_refactoring/my_button/my_button.dart';
class LogIn extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text(
'Sign In',
style: TextStyle(color: Colors.white),
),
centerTitle: true,
),
body: buildButton(),
);
}
Widget buildButton() {
return Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyButton(image: Image.asset('images/glogo.png'),
text: Text(
'Login with Google',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
color: Colors.white,
radius: 4.0,
onPressed: (){}
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/flogo.png'),
Text(
'Login with Facebook',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/flogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.blueAccent,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
)
),
),
SizedBox(
height: 10.0,
),
ButtonTheme(
height: 50.0,
child: ElevatedButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/mlogo.png'),
Text(
'Login with Email',
style: TextStyle(
color: Colors.black87,
fontSize: 15.0),
),
Opacity( //child로 똑같은 구글 이미지를 불러오는 이미지값 asset을 가지고 있음
opacity: 0.0,
child: Image.asset('images/mlogo.png'),
),
],
),
style: ElevatedButton.styleFrom(
primary: Colors.green,
),
onPressed: () {},
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(4.0),
),
),
),
],
),
);
}
}