이번 여름 방학에는 감사하게도 SKT FLY AI 3기에 참여하게 됐다!
교육기간 중 마지막 3주동안에 프로젝트를 진행하게 되는데, 글쎄 클라이언트 개발자가 없는 것....
그렇게 플러터를 배워보기로 했다

이 글은 최지호님의 코드팩토리의 플러터 프로그래밍을 보고 정리한 글입니다.

1.1 다트 소개

다트

구글이 2011년 10월 GOTO 컨퍼런스에서 공개
자바스크립트를 대체하려는 시도를 했지만 웹 개발에 혼란을 가져온다는 여론을 극복하지 못하고
결국 다트 언어를 자바 스크립트로 완전 컴파일 가능하게 하는데 그쳤다.
하지만 현재 플러터의 인기에 힘입어 모바일 영역에서 다트언어가 큰 각광을 받고 있다.

다트의 장점

  • UI를 제작하는데 최적화되어있음
  • 완전한 비동기 언어, 이벤트 기반, isolate를 이용한 동시성 기능까지 제공
  • Null Safety, Spread Operator, Collection if 등 효율적으로 UI를 코딩할 수 있는 기능을 제공ㅇ해줌
  • 효율적인 개발환경 제공 → 핫 리로딩 : 코드의 변경사항을 즉시 화면에 반영
  • 멀티 플랫폼에서 로깅, 디버깅, 실행 가능
  • AOT 컴파일 가능 → 빠른 속도
  • 자바스크립트로의 완전한 컴파일을 지원
  • 백엔드 프로그램이 지원

JIT & AOT

JIT 컴파일 방식

  • Just in Time
  • 핫 리로딩
    다트 가상머신에서 제공하는 기능으로 코드의 변경된 사항을 다시 컴파일할 필요 없이 즉시 화면에 반영할 수 있음

➡️ 개발 시 사용

AOT 컴파일 방식

  • Ahead of Time
  • ARM64나 x64 기계어로 다트언어가 직접 컴파일 됨 리소스를 효율적으로 사용
    ➡️ 배포 시 사용

1.2 문법 공부 환경

다트 패드

https://dartpad.dev


안드로이드 스튜디오

  1. 플러터 프로젝트 생성
  2. main.dart 파일에 생성되는 기본 코드 모두 삭제하고 다음과 같이 main() 함수 만 남긴다.
void main(){
	print("Welcome to DuDu's velog");
}
  1. Terminal 탭 실행
  2. dart lib/main.dart 명령어 실행

1.3 기초 문법

변수 선언

var

var 변수명 = 값 형식으로 선언
✅ 타입 추론 기능 제공 → 명시적으로 타입 선언 불필요
✅ 한번 추론된 타입은 고정됨

void main() {
  var name = "DuDu";
  print(name);
  
  //변경가능
  name = "velog";
  print(name);
}

dynamic

✅ 변수의 타입이 고정되지 않음

void main() {
  dynamic name = "DuDu";
  print(name);
  
  //type 변경 가능
  name = 2071003;
  print(name);
}

final/const

❌ 처음 선언 후 변수 값을 변경할 수 ❌

final

  • 런타임 상수
    = 실행해봐야 값을 알 수 있음
    ➡️ 코드가 실행될 때 값이 확정되면 사용
void main() {
  //코드가 실행되는 시간
  final DateTime now = DateTime.now();
  print(now);
}

const

  • 빌드타임 상수
    ➡️ 코드를 실행하지 않은 상태에서 값이 확정되면 사용
void main() {
  //에러
  const DateTime now = DateTime.now();
  print(now);
}

변수 타입

var 키워드를 사용하면 자동으로 타입을 유추할 수 있지만,
직접적으로 명시해주면 더욱 직관적이여서 유지보수가 편함

  • 문자열 String
  • 정수 int
  • 실수 double
  • 불리언 bool

1.4 컬렉션

컬렉션이란?

여러 값을 하나의 변수에 저장할 수 있는 타입

  • 여러 값을 순서대로 저장 (List)
  • 특정 키값을 기반으로 빠르게 값을 검색 (Map)
  • 중복된 데이터를 제거 (Set)
    할 때 사용

✅ 서로의 타입으로 자유롭게 형변환이 가능

List

여러 값을 순서대로 한 변수에 저장할 때 사용

  • 원소 = 리스트의 구성 단위
  • 리스트명[인덱스] 형식으로 접근
  • 인덱스는 0부터 시작
void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  print(duduFoodList);
  print(duduFoodList[0]);
  print(duduFoodList[3]); //Last element
  
  //Print Length of List
  print(duduFoodList.length);
  
  //Change value
  duduFoodList[3] = "분짜";
  print(duduFoodList);
}

➡️ 리스트 길이는 length로 확인 가능

add()

List에 값을 추가할 때 사용

void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  duduFoodList.add("케이크");

  print(duduFoodList);
}

where()

List에 있는 값들을 순서대로 순회하면서 특정 조건에 맞는 값만 필터링하는데 사용

  • 매개변수에 함수 입력
  • 입력된 함수는 기존 값을 하나씩 매개변수로 입력 받음
  • 각 값별로 true이면 값을 유지, 아니면 버림
  • 순회가 끝나면 유지된 값들을 기반으로 이터러블 반환
void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  final newList = duduFoodList.where(
  (food) => food == '떡볶이' || food == '마라탕',);
  
  //iterable
  print(newList);
  //Change to List
  print(newList.toList());
}

map()

List에 있는 값들을 순서대로 순회하면서 값을 변경 가능

  • 이터러블 반환
void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  final newFood = duduFoodList.map(
  (food) => '두두가 좋아하는 $food',); //모든 값 앞에 '두두가 좋아하는' 추가
  
  //iterable
  print(newFood);
  //Change to List
  print(newFood.toList());
}

reduce()

  • 순회하면서 매개변수에 입력된 함수를 실행
    ✅ 순회할 때마다 값을 쌓아감
  • List 멤버의 타입과 같은 타입을 반환

✅ 두개의 매개변수 입력

  • 첫번째 순회
    - 첫번째 매개변수(value) = 리스트 첫번째값
    - 두번째 매개변수(element) = 리스트 두번째값
  • 첫번째 순회 이후
    - 첫번째 매개변수(value) = 기존 순회에서 반환한 값
    - 두번째 매개변수(element) = 리스트의 다음 값
void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  final allFood = duduFoodList.reduce(
  (value, element) => value + ', '+element); 
  
  //Return Type of List's element == String
  print(allFood); //떡볶이, 마라탕, 치킨, 쌀국수
}

fold()

  • reduce()와 논리는 동일
  • reduce()는 함수가 실행되는 리스트 요소들의 타입이 같아야 하지만
    fold()는 어떤 타입이든 반환 가능

✅ 2개의 매개변수

  • fold함수의 첫번째 매개변수에 입력된 값이 value의 초깃값으로 사용
void main() {
  List<String> duduFoodList = ['떡볶이', '마라탕', '치킨', '쌀국수'];
  
  final allFood = duduFoodList.fold<int>( //int형 반환
    0, (value, element) => value +element.length); 
  
  print(allFood); //11
}

Map

key와 value의 짝을 저장

순서대로 값을 저장하는데 중점을 둔 List와 달리,
키를 이용해 원하는 값을 빠르게 찾는 데 중점을 둠

void main() {
  Map<String, String> dictionary = {
    'Welcome' :  '환영하다',
      'to' : '에',
    'DuDu\'s' : '두두의',
      'velog':'벨로그'
  };
  
 print(dictionary['DuDu\'s']);
 print(dictionary['velog']);
}
 
  • 키와 값 모두 반환 가능
    • 받환받고 싶은 타입의 변수에 key와 value 게터를 실행
    • iterable 타입으로 반환
void main() {
  Map<String, String> dictionary = {
    'Welcome' :  '환영하다',
      'to' : '에',
    'DuDu\'s' : '두두의',
      'velog':'벨로그'
  };
  
 print(dictionary.keys); //Iterable
 print(dictionary.values.toList()); //List
}
 

Set

중복 없는 값들의 집합

Set<타입> 세트이름 형식으로 생성
중복을 방지하므로 유일한 값들만 존재하는 것을 보장

void main() {
  Set<String> duduFood = {'떡볶이', '마라탕', '치킨', '쌀국수', '떡볶이'}; //떡볶이 중복

  print(duduFood);
  print(duduFood.contains("마라탕")); //마라탕 있는지 확인
  print(duduFood.toList()); //Set -> List 
  
  List<String> duduFood2 = ['마라탕', '치킨', '치킨'];
  print(Set.from(duduFood2)); //List -> Set
}
 

✏️ 컬렉션 타입들의 진정한 장점은 서로의 타입으로 형변환하며 나타남

  • Set에 toList() → List
  • Map에 .keys.toList || .values.toList() → List
  • Set.from() → 어떤 리스트든 set으로 변환 가능
    (물론 set의 특성에 따라 중복값은 제거됨)

enum

한 변수의 값을 몇가지 옵션으로 제한
선택지가 제한적일 때 사용!

String으로 완전 대체할 수 있지만,
enum은 기본적으로 자동완성이 지원되고 정확히 어떤 선택지가 존재하는지 정의해둘 수 있기 때문에 유용하다!

enum Status{
    approved,
    pending,
    rejected,
}

void main() {
  Status status = Status.approved;
  print(status);
  
}


1.5 연산자

기본 수치 연산자

다른 언어에서도 사용하는 기본 산수 기능 제공

  • 덧셈 +
  • 뺄셈 -
  • 곱셉 *
  • 나눗셈 - 몫 /
  • 나눗셈 - 나머지 %
  • 단항연산자도 가능
    ++ -- += -= *= /=

null 관련 연산자

null = 아무것도 없음
(0은 0이라는 값을 가짐)

타입 뒤에 ?를 추가해줘야 null값 저장 가능

void main() {
  //타입 뒤에 ?를 명시 == null값 가질 수 있음
  double? num1 = 1;
  num1 = null;
  
  //double num2 = null; //ERROR
  
}

null을 가질 수 있는 변수에 새로운 값을 추가할 때
??사용하면 기존에 null인 때만 값이 저장되도록 할 수 있음

void main() {
  double? num; //자동으로 Null
  print(num);
  
  num ??= 3; // 기존값 == null → 변경
  print(num);
  
  num ??= 10; //기존값 == 3 → 변경X
  print(num); 
  
  //??와 = 를 띄어쓰면 오류
  
}

값 비교 연산자

다른 언어와 동일

  • >
  • <
  • >=
  • <=
  • !=
  • ==

타입 비교 연산자

is 사용

void main() {
  int a = 1;
  print(a is int); //True
  print(a is String); //False
  print(a is! int); //False
  print(a is! String); //True
}

논리 연산자

  • and &&
  • or ||


1.6 제어문

if

원하는 조건을 기준으로 다른 코드를 실행하고 싶을 때 사용
if, else if, else문의 순서대로 괄호 안에 작성한 조건이 true이면 해당 조건의 코드 블록이 실행된다.

switch

입력된 상수값에 따라 알맞은 case 블록을 수행
break 키워드를 사용하면 switch문 밖으로 나갈 수 있다.

✅case 끝에 break를 사용해야한다.
빼먹으면 컴파일 에러남.

enum과 함께 사용하면 유용하다.

for

여러번 반복해서 실행할 때 사용

  • 횟수 기반

while과 do...while

for과 동일하게 반복적인 작업 실행 시 사용

  • 조건 기반
    조건이 true면 계속 실행. false면 멈춤

do while문의 경우 반복문을 실행한 뒤에 조건을 확인한다.



1.7 함수와 람다

일반적인 함수

  • 함수는 한번만 작성, 여러 곳에서 재활용 가능
  • 반환할 값이 없다면 void 키워드 사용
int addNum(int a, int b){
  return a+b;
}
void main() {
  print(addNum(1,7));
}

매개변수 지정 방법

포지셔널 파라미터

  • 순서가 고정된 파라미터
  • 입력된 순서대로 매개변수에 값이 지정
  • 위에서 선언한 addNum함수와 같은 형태

네임드 파라미터

  • 이름이 있는 파라미터

  • 지정하려면 중괄호 {}required 키워드 사용


✏️ addNum함수를 네임드 파라미터 방식으로 변환해보자!

int addNum({required int a, required int b}){
  return a+b;
}
void main() {
  print(addNum(a:1,b:7));
}

required 키워드
매개변수가 널값이 불가능한 타입이면 기본값을 지정해주거나 필수로 입력해야한다!
는 의미..


✏️ 그렇다면 기본 값을 갖는 포지셔널 파라미터를 지정해보자

int addNum( int a, [ int b = 2 ]){
  return a+b;
}
void main() {
  print(addNum(1));
}
\

✏️ 그렇다면 기본 값을 갖는 네임드 파라미터!

int addNum({required int a,  int b=2}){
  return a+b;
}
void main() {
  print(addNum(a:1));
}

‼️ 포지셔널과 네임드를 섞어서 사용하는 것도 가능함
다만, 포지서녈 파라미터가 네임드보다 반드시 먼저 위치할 것

int addNum(int a , {required int b,  int c = 7}){
  return a+b+c;
}
void main() {
  print(addNum(1, b:4, c: 4));
}

익명 함수와 람다 함수

공통점

  • 두 함수 모두 이름이 없음
  • 일회성으로 사용 됨

통상적으로 많은 언어에서 익명함수와 람다함수를 구분하고 있지만
dart에서는 구분하지 않는다.

익명 함수

▶️ 기본적인 형태

(매개변수){
	함수 바디
}

▶️ 리스트의 모든 값을 더하는 함수

void main() {
  List<int> nums = [1,2,3,4,5];
  
  final allMembers =nums.reduce((value, element){
    return value+element;
  });
  
  print(allMembers);
}

람다 함수

▶️ 기본적인 형태

(매개변수) => 단 하나의 스테이트먼트

▶️ 리스트의 모든 값을 더하는 함수

void main() {
  List<int> nums = [1,2,3,4,5];
  
  final allMembers =nums.reduce((value, element) => value + element);
  
  print(allMembers);
}

typedef와 함수

  • typedef
    함수의 시그니처(리턴 타입)를 정의
    무슨 동작을 하는지 정의하지 않음
typedef Operation = void Function(int x, int y);

✅ 다트에서 함수는 일급 객체(First-class citizen)이므로 값처럼 사용할 수 있음
따라서 다음과 같이 매개변수로 넣어서 사용!

typedef Operation = void Function(int x, int y);

void add (int x, int y){
  print('결괏값 : ${x+y}');
}

void calculate(int x, int y, Operation oper){
  oper(x,y); 
}

void main(){
  calculate(1, 2, add); //add함수를 매개변수로
}

1.8 try...catch

  • 특정 코드의 실행을 시도(try)해보고 문제가 있다면 에러를 잡아라(catch)!

▶️ 예시코드

void main(){
  try{
    final String name = 'DuDu';
    //에러가 발생하지 않으므로 정상출력
    print(name);
    
  }catch(e){ 
    print(e);
  }
}

▶️ throw를 이용해 에러 발생

void main(){
  try{
    final String name = 'DuDu';
    
    //고의적 에러 발생
    throw Exception('It is a wrong name');
    
    print(name);
    
  }catch(e){ //에러 발생 -> catch 출력
    print(e);
  }
}


profile
멋쟁이가 될테야

0개의 댓글

Powered by GraphCDN, the GraphQL CDN