Flutter 개발에는 다트 언어를 사용한다. 온라인 메뉴얼에서 자세한 공부 자료를 얻을 수 있다.
https://dart.dev/guides/language/language-tour
개발 도구를 추가로 설치하지 않고 다트패드 웹사이트를 활용하여 온라인에서 다트 코드를 실행할 수 있다.
기본 문법
- 다트 문법 역시 main 함수가 엔트리 포인트다.
- 주석
// 한줄 주석
/* 여러줄 주석 */
/// 는 HTML 형식의 문서를 생성할 수 있는 주석
- 변수
// 문자열 묶음 기호로 작은따옴표와 큰따옴표 모두를 사용할 수 있다.
String name = '홍길동';
String hobby = "축지법";
int i = 10;
double d = 10.0;
// int와 double은 num 타입에 포함된다. 따라서 아래와 같이 할 수도 있다.
num a = 10;
num b = 20.0;
// 일부 언어에서, double 타입에 int 타입을 대입하는 자동 형변환을 지원하기도 하지만
// 다트에서는 지원하지 않는다. 따라서 아래는 오류를 발생시킨다.
int a = 10;
double b = a; // 에러
// 반면 num 타입에는 int와 double 타입 모두 대입할 수 있다.
int a = 10;
double b = 20.0;
num c = a; // ok
c = b; // ok
- 다트는 타입을 직접 명시하지 않고 var로 대체할 수 있는 타입 추론을 지원한다. 일반적으로 이 방법을 주로 사용한다
( 자바스크립트의 var과 비슷하지만 완전히 같지는 않다. 자바스크립트의 var는 Dart에서 dynamic과 같다https://zucca.tistory.com/106 )
var i = 10; // int
var d = 10.0; // double
var s = 'hello'; // 문자열
var s2 = "hello"; // 문자열
var b = true; // 불리언
- 상수 final, const
final String name = '홍길동';
name = '임꺽정'; // 에러
- assert() 함수는 계산 결과가 참인지 검사
assert(2 + 3 == 5); // 결과 : true
assert(5 - 2 == 3); // 결과 : true
- 자바와 마찬가지로 더하기 연산으로 문자열 결합 가능
String str = 'hello' + 'world';
- is 키워드를 통해 타입을 검사할 수 있다.
// is : 같은 타입이면 true
// is! : 다른 타입이면 true
int a = 10;
if(a is int) {
print('정수');
}
String text = 'hello';
if(text is! int) {
print('숫자가 아님');
}
- 형변환은 as 키워드를 사용한다. 전혀 관련없는 타입으로는 당연히 안된다.
// int와 double은 모두 num을 구현하는 타입이지만 서로는 관계가 없기 때문에 형변환 불가
var c = 30.5; // double
int d = c as int; // 에러
하지만 int와 double 모두 상위 개념인 num으로 형변환 가능
dynamic d = 30.5;
num n = d; // as num; 생략 가능
- 함수의 형태
int f(int x) {
return x + 10;
}
void main() {
var result = f(10);
}
- 변수 앞에 $ 기호를 붙여 문자열 내에 변수 삽입 가능. 또한 $기호 뒤에 {}로 둘러싸 수식을 포함한 각종 표현식 사용 가능.
String _name = '홍길동';
int _age = 20;
void main() {
print('$_name은 $_age살입니다.');
print('$_name은 ${_name.length} 글자입니다.');
print('10년 후에는 ${_age + 10}살입니다.');
}
--------
OUTPUT
홍길동은 20살입니다.
홍길동은 3 글자입니다.
10년 후에는 30살입니다.
뭔가 왠만하면 그냥 다 ${변수} 로 사용하는게 안전할 것 같다.. 내생각엔!
- 클래스 밖에 작성하는 함수를 최상위 함수라고 한다. 최상위 함수는 어디에서나 호출할 수 있는 함수이며 main 함수처럼 가장 바깥에 작성되어 있는 함수다. => 멤버함수 = 메서드 가 아닌 함수를 말하는 것.
// 최상위 함수
bool isEven(int number) {
return number % 2 == 0;
}
void main() {
print(isEven(10));
}
- 클래스 내부에 작성하는 함수를 메서드라고 부른다.
class MyClass {
// 메서드
bool isEven(int number) {
return number % 2 == 0;
}
}
void main() {
var myClass = MyClass();
print(myClass.isEven(10));
}
- 클래스 내부에 선언된 함수라도 static 키워드를 붙이면 정적 메서드가 되며 함수로 볼 수 있다.
( static 키워드가 붙은 함수는 최상위 함수처럼 사용할 수 있다)
class MyClass {
// 정적 메서드, 함수
static bool isEven(int number) {
return number % 2 == 0;
}
}
void main() {
// 인스턴스 없이 가능
print(MyClass.isEven(10));
}
- 클래스 예제
void main() {
Person person = Person("obj", 39);
person.greeting();
}
class Person {
String name;
int age = 0;
Person(this.name, this.age);
void greeting() {
print('안녕하세요 저는 $name입니다.');
}
}
// https://papabee.tistory.com/37
- 익명 함수
([인수명]) { [동작 또는 반환값] }
void main() {
var x = (var number) {
return number % 2 == 0;
};
print(x(10));
}
- 람다식
([인수명]) => [동작 또는 반환값]
void main() {
var func = (var number) => number % 2 == 0;
print(func(10));
}
dart 람다에 대한 내용 : 여기
- 선택 매개변수
함수 정의에서 {}로 감싼 매개변수는 선택적으로 사용할 수 있다.
호출할 때 매개변수명을 값 앞에 써주게 된다. 따라서 이런 매개변수를 'Named Parameter' 라고 부른다.
// 아래처럼 하면 플러터 2에서 오류가 나온다. 널 세이프티가 적용되지 않았기 때문.
// 책이 플러터2 대응이라 해놓으시고, 플러터2에 대한 단원만 넣고 전체적인 내용은 수정하지 않은 것 같다. 당했다.
void something({String name, int age}) {}
void main() {
something(name: '홍길동', age: 10);
}
// 선택 매개변수의 특성상 선택적으로 사용할 수 있기 때문에
// 값들에 널이 들어갈 수 있음을 명시적으로 알려야 한다. (플러터 2는 널 세이프티이기 때문)
// 타입 뒤에 ?를 붙이면 null을 허용하는 타입이란 말이 된다.
void something({String? name, int? age}) {}
void main() {
something(name: '홍길동', age: 10);
}
// 또는 초기값을 지정해줘서 null이 발생할 일이 없도록 하면 된다.
void something(String name, {int age = 10}) {}
void main() {
something('홍길동', age: 10);
}
선택 매개변수 중에 필수로 받아야 하는 것이 있다면 앞에 required를 넣어준다.
required는 named parameter중 필수 파라미터를 나타낸다. (예전엔 어노테이션이었는데, 바뀜)
+ 선택 매개변수( {}로 싸여있는것 )의 경우엔 이름있이(age: 10) 적어줘야한다는 것은 알겠는데,
굳이 그냥 매개변수(필수)의 경우에도 이름있이 적어주면 어떻게 될까 궁금해서 해봤다.
class hello {
void hi(int a, {int? b}) {
}
}
void main() {
hello a = hello();
// a.hi(a: 10, b: 5); // 컴파일 안됨.
a.hi(10, b: 5); // 이렇게 해야 됨.
}
결론 : 선택 매개변수의 경우에만 이름있이 넣어줘야 하는게 규칙이다.
- 만약 필수 매개변수와 선택 매개변수를 함께 사용하려면, 앞쪽에 필수 매개변수를 먼저 둔다.
void something(String name, {int? age}) {}
void main() {
something('홍길동', age: 10); // ok
something('홍길동'); // ok
something(age: 10); // 에러
something(); // 에러
}
- 위에서 잠깐 봤듯, 선택 매개변수는 기본값을 지정할 수 있다.
void something(String name, {int age = 10}) {}
void main() {
something('홍길동', age: 10);
}
난 null safty 하기 위해서 오히려 이 방법이 더 좋은 것 같다.
- if else 문
void main() {
String text = 'hello';
if(text is int) {
print('정수');
} else if (text is double) {
print('실수');
} else {
print('정수도 실수도 아님');
}
}
- 삼항 연산자
// isRainy 값에 따라
var todo = isRainy ? '빨래를 하지 않는다' : '빨래를 한다';
- 스위치 케이스 문
enum Status { Uninitialized, Authenticated, Authenticating, Unauthenticated }
void main() {
var status = Status.Authenticated;
switch(status) {
case Status.Authenticated:
print('인증됨');
break;
case Status.Authenticating:
print('인증 처리 중');
break;
case Status.Unauthenticated:
print('미인증');
break;
case Status.Uninitialized:
print('초기화됨');
break;
}
}
- 포문 (반복문)
void main() {
var items = ['짜장', '라면', '볶음밥'];
for (var i = 0; i < items.length; i++) {
print(items[i]);
}
}
- 클래스 기본
class Person {
// Flutter 2부터 Null Safty를 위해 ?를 붙여주어야 한다.
String? name;
// 또는 초기값 명시.
int age = 0;
Person() {
name = "Tom";
age = 0;
}
void addOneYear() {
age++;
}
}
void main() {
var person = new Person();
// var person = Person(); // new 키워드 생략가능. 일반적으로 생략
person.addOneYear();
print(person.name);
print(person.age);
print('${person.age}살');
}
- 접근 지정자
// 변수명 앞에 _ 기호를 붙이면 '외부' 에서 접근이 불가능하다.
// 여기서의 외부는 자바의 private와 다르다.
// 동일 파일 내에서는 접근이 가능하다는 차이가 있다.
class Person {
String? name;
int? _age;
}
void main() {
var person = new Person();
person._age = 20;
}
다트에서 말하는 private는 동일 파일이 아닌 기준이어서
자바에서 말하는 private와는 다르다.
-----------------
Dart에서의 private : 해당 클래스가 정의되어 있지 않은 다른 파일에서 접근 불가.
하지만 정의되어 있는 파일 내에서는 여전히 접근 가능.
+ ? 와 ! 의 차이 : 널일수도있어요? 와 널아니라고! 의 차이
- 생성자
아무것도 추가하지 않을 시 기본 생성자는 디폴트로 있긴 하지만, 사용자 정의 생성자를 추가할 경우 기본 생성자를 사용할 수 없게 된다는 치명적인 단점이 있다. 선택 매개변수를 사용하여서 이를 막을 수도 있지만 좋은 방법은 아니다. (어쨌든 동일한 생성자가 호출되므로)
class Person {
int? age = 0;
Person({this.age});
}
void main() {
var person = Person();
var pserson = Person(age: 10);
}
- getter, setter 키워드
set get 키워드를 사용한 이러한 특수한 유형의 메서드를 사용할 수 있다. private인 _ 와 함께 사용할 때 좋다.
class Rectangle {
num left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
num get right => left + width;
set right(num value) => left = value - width;
num get bottom => top + height;
set bottom(num value) => top = value - height;
}
void main() {
var rec = Rectangle(0, 0, 20, 20);
rec.bottom = 10;
print(rec.right);
}
- 상속
컴퓨터 세계의 상속은 슈퍼클래스를 그대로 복사한 후 기능 추가나 변경이 첨가됩니다. -> 음.. 확실히 동의는 안하지만, 개념상ㅇㅇ..
class Hero {
String name = '영웅';
void run() {}
}
class SuperHero extends Hero { // Hero를 상속
// 부모의 run() 메서드를 다시 정의(오버라이드)
@override
void run() {
super.run(); // 부모의 run()을 실행
this.fly(); // 추가로 fly()도 실행
}
void fly() {}
}
void main() {
var hero = SuperHero();
hero.run();
hero.fly();
print(hero.name); // 영웅
}
- 추상 클래스
추상 클래스는 추상 메서드를 포함하는 클래스. 추상 메서드 : 선언만 되고 정의가 없는 메서드
추상 클래스는 그대로 인스턴스화할 수 없으며 다른 클래스에서 implement 하여 기능을 완성하는 상속 재료로 사용됩니다.
abstract class Monster {
void attack();
}
class Goblin implements Monster {
@override
void attack() {
print('고블린 어택');
}
}
class Bat implements Monster {
@override
void attack() {
print('할퀴기');
}
}
- 믹스인
with 키워드를 사용하면, 상속하지 않고 다른 클래스의 기능을 가져오거나 오버라이드할 수 있다.
class Hero {
String name = '영웅';
void run() {}
}
abstract class Monster {
void attack();
}
class Goblin implements Monster {
@override
void attack() {
print('고블린 어택');
}
}
class DarkGoblin extends Goblin with Hero {
}
- 열거 타입
열거 타입은 switch와 찰떡궁합이다.
enum Status { login, logout }
void main() {
var authStatus = Status.logout;
switch(authStatus) {
case Status.login:
print('로그인');
break;
case Status.logout:
print('로그아웃');
break;
}
}
- 컬렉션
다트는 List, Map, Set 등의 Collection을 제공한다.
List : 같은 타입의 자료를 여러 개 담을 수 있고, 특정 인덱스로 접근할 수 있다.
Map : 키와 값의 쌍으로 저장할 수 있고, 키를 통해 값을 얻을 수 있다.
Set : 중복이 허용되지 않고, 찾는 값이 있는지 없는지 판단하고자 할 때 사용한다.
+ 다트에는 배열이 없다.
List
void main() {
List<String> items = ['짜장', '라면', '볶음밥'];
// var items = ['짜장', '라면', '볶음밥'];
items[0] = '떡볶이';
for(var i = 0; i < items.length; i++) {
print(items[i]);
}
}
기본선언
List<type> lst = [];
스프레드 연산자 '...'
... 연산자는 컬렉션을 펼쳐주는 연산자로, spread 연산자라고 한다. 다른 컬렉션 안에 컬렉션을 삽입할 때 사용한다.
void main() {
var items = ['짜장', '라면', '볶음밥'];
var items2 = ['떡볶이', ...items, '순대'];
for(var i = 0; i < items2.length; i++) {
print(items2[i]);
}
}
List를 Set에 담게 되면 자동으로 중복 제거의 효과도 얻을 수 있다.
void main() {
final items = [1, 2, 2, 3, 3, 4, 5];
final myNumbers = {...items, 6, 7};
}
Map
순서가 없고 탐색이 빠른 컬렉션. 키와 값의 쌍으로 이루어져 있다.
void main() {
// Map<String, String> cityMap = {
// '한국': '부산',
// '일본': '도쿄',
// '중국': '북경'
// };
var cityMap = {
'한국': '부산',
'일본': '도쿄',
'중국': '북경'
};
cityMap['한국'] = '서울';
}
Set
집합을 표현하는 컬렉션. 중복을 허용하지 않음.
void main() {
// Set<String> citySet = {'서울', '수원', '오산', '부산'};
var citySet = {'서울', '수원', '오산', '부산'};
citySet.add('안양'); // 추가
citySet.remove('수원'); // 삭제
citySet.contains('서울'); // true
}
- 일급 객체
다트에서는 함수를 값으로 취급할 수 있으므로, 다른 변수에 함수를 대입할 수 있다.
void greeting(String text) {
print(text);
}
void main() {
var f = greeting;
f('hello');
}
- 다른 함수의 인수로 함수 자체를 전달하거나 반환받을 수도 있다.
void something(Function(int i) f) {
f(10);
}
void main() {
int value = 10;
// 익명함수 활용
something((value) {
print(value);
});
}
- 다트에서 함수로 표현할 수 있는 것들(람다식, 익명함수, 메서드)은 모두 값으로 취급할 수 있다.
void something(Function(int i) f) {
f(10);
}
void myPrintFunction(int i) {
print('내가 만든 함수에서 출력한 $i');
}
void main() {
// 동일한 결과
something(myPrintFunction);
something((i) => myPrintFunction(i));
something((i) => print(i));
something(print);
}
- forEach 함수
내부적으로 반복을 수행한다. forEach() 함수는 (E element) {} 형태의 함수를 인수로 받는다. (E는 모든 타입이 가능하다는 뜻)
void main() {
final items = [1, 2, 3, 4, 5];
items.forEach(print); //1, 2, 3, 4, 5
// 익명함수로 표현하면
items.forEach((e) {
print(e);
});
// 람다식으로 표현하면
items.forEach((e) => print(e));
}
(e) => print 형태의 함수에서 e는 items의 각 요소가 내부적으로 반복하면서 하나씩 들어올 인수이다.
- where 함수
조건 필터링에 사용하는 함수. 짝수만 출력하고 싶을 때의 예시는 아래와 같다.
void main() {
final items = [ 1, 2, 3, 4, 5 ];
// for문으로 구현 예시
for(var i = 0; i < items.length; i++) {
if(items[i] % 2 == 0) {
print(items[i]);
}
}
// where 함수로 구현 예시
items.where((e) => e % 2 == 0).forEach(print);
}
- toList
결과를 리스트로 저장
void main() {
var items = [1, 2, 3, 4, 5];
final result = [];
items.forEach((e) {
if(e % 2 == 0) {
result.add(e);
}
});
// 위 코드를 toList로 한줄로 구현 가능
final results = items.where((e) => e % 2 == 0).toList();
}
참고로, final 키워드를 쓰면 타입 생략 가능.
- toSet
리스트에 중복된 데이터가 있을 경우 중복을 제거한 리스트를 얻고 싶을 수 있다.
void main() {
final items = [1, 2, 2, 3, 3, 4, 5];
var result = [];
for(var i = 0; i < items.length; i++) {
if(items[i] % 2 == 0) {
result.add(items[i]);
}
}
print(result); // 2, 2, 4
//위 코드와 동일한 코드는
final result2 = items.where((e) => e % 2 == 0).toList(); // 2, 2, 4
// 결과에 중복을 피하기 위해서 Set에 먼저 담으면 된다.
final result3 = items..where((e) => e % 2 == 0).toSet().toList(); // 2, 4
}
+ var을 사용하여 빈 Set이나 빈 Map을 작성할 때는 문법을 조심해야 한다. 값 없이 그냥 {} 만 작성하면 Set이 아닌 Map으로 인식한다.
var mySet = <String>{}; // Set<String> mySet
var myMap = {}; // Map<dynamic, dynamic>
- any()
any() 함수는 리스트에 특정 조건을 충족하는 요소가 있는지 없는지 검사할 때 사용한다.
다음은 리스트에 짝수가 하나라도 있는지 검사하여 결과를 출력하는 코드이다.
void main() {
final items = [1, 2, 2, 3, 3, 4, 5];
print(items.any((e) => e % 2 == 0)); // true;
}
- 계단식 표기법 '.. 연산자'
계단식 표기법(cascade notation)을 사용하면, 동일 객체에서 일련의 작업을 수행할 수 있다.
컬렉션의 add() 메서드는 void를 반환하고, remove() 메서드는 bool을 반환하지만 .. 연산자를 사용하면 연쇄 작업이 가능하다.
void main() {
final items1 = [1, 2, 3, 4, 5];
final items2 = [1, 2, 3, 4, 5];
// 기존 방식
items1.add(6);
items1.remove(2);
print(items1);
// 새로운 방식
print(items2..add(6)..remove(2));
}
- 컬렉션 if
다트에서는 컬렉션 내부에 if문이나 for문을 사용할 수 있다. (이 때 {} 블록은 사용할 수 없다)
void main() {
bool promoActive = true;
print([1, 2, 3, 4, 5, if (promoActive) 6]);
}
- 컬렉션 for
컬렉션 내부에 for문을 사용할 수도 있다.
void main() {
var listOfInts = [1, 2, 3];
var listOfStrings = [ '#0', for (var i in listOfInts) '#$i'];
}
- 다트에서는 모든 것이 객체이다 (파이썬처럼). 심지어 int double bool 같은 타입도 모두 클래스이다. 이러한 타입들은 모두 null을 가질 수 있다. 따라서 다트에서 모든 타입은 null일 수 있다.
서적 : 오준석의 플러터 생존코딩
아쉬운점 : 플러터 2 대응이라는데,대응이라고 하기에는 플러터 2 단원에 대한 설명 단원만 넣고
전체적인 책의 내용에서는 플러터 2에 맞게 수정되지 않아서, 돌아가지 않는 예제들이 꽤 있다...
'[Flutter]' 카테고리의 다른 글
[Flutter] 앱 구조 (0) | 2021.08.14 |
---|---|
[Flutter] 프로젝트 구조 (0) | 2021.08.13 |
[Flutter] 개발 환경 진단 (0) | 2021.08.08 |
[Flutter] Flutter의 Hot Reload 기능 (0) | 2021.08.08 |
[Flutter] 첫 Flutter Project 생성해보기 (0) | 2021.08.08 |