
플러터에서 앱 이름은 만들었던 프로젝트의 이름을 기본 앱 이름으로 만들어줘서 바꿔줘야 함
android/app/src/main/AndroidManifest.xml
AndroidManifest 를 수정해준 뒤 안드로이드 에뮬레이터에서 반드시 앱 삭제 후 재설치
안드로이드 앱의 핵심 설정 파일로, 앱이 어떻게 동작할지를 Android 시스템에 알리는 데 사용. 간단히 말해, 앱의 구조, 권한, 컴포넌트 등을 선언하는 문서
android label
ios/Runner/Info.plist
CFDisplayBundleDisplayName
밑에 key 값 수정
다양한 해상도와 디바이스 환경에서 앱 아이콘이 선명하게 보이도록 하기 위해서 각각 사이즈에 맞는 아이콘 만들어줘야함
| 플랫폼 | 용도 / 위치 | 크기 (px) | 비고 |
|---|---|---|---|
| Android | mipmap-mdpi | 48×48 | 기본 해상도 (160 dpi) |
| Android | mipmap-hdpi | 72×72 | |
| Android | mipmap-xhdpi | 96×96 | 초고해상도 (320 dpi) |
| Android | mipmap-xxhdpi | 144×144 | 초초고해상도 (480 dpi) |
| Android | mipmap-xxxhdpi | 192×192 | 초초초고해상도 (640 dpi) |
| Android | Play Store 아이콘 | 512×512 | 앱스토어 제출용 (투명 배경 금지) |
| Android | Adaptive 아이콘 (foreground/background) | 108×108 (권장) | Android 8.0 이상, 벡터 사용 권장 |
| iOS | Notification (20pt @1x, @2x, @3x) | 20×20, 40×40, 60×60 | 알림용 |
| iOS | Settings (29pt @1x, @2x, @3x) | 29×29, 58×58, 87×87 | 설정 아이콘 |
| iOS | Spotlight (40pt @2x, @3x) | 80×80, 120×120 Spotlight | 검색용 |
| iOS | 앱 아이콘 (60pt @2x, @3x) | 120×120, 180×180 | 홈화면 기본 아이콘 |
| iOS | iPad 아이콘 (76pt @1x, @2x) | 76×76, 152×152 | iPad 홈화면 |
| iOS | iPad Pro 아이콘 (83.5pt @2x) | 167×167 | iPad Pro 전용 |
| iOS | App Store 아이콘 | 1024×1024 | 제출용, 배경 없음 / 정사각형 필수 |
512x512 사이즈의 아이콘만 프로젝트에 넣고 패키지 명령어 실행만 하면
→ 변환 후 프로젝트 내 파일 복사까지 다 해줘요!
flutter_launcher_icons 패키지 추가-d 옵션 : --dev의 줄임말로, 해당 패키지를 개발 중에만 사용하고 실제 앱에서는 포함되지 않도록 설정flutter pub add -d flutter_launcher_icons
pubspec.yaml 파일 가장 하단에 아래 내용 추가flutter_launcher_icons:
android: true
ios: true
image_path: "logo.png"
dart run flutter_launcher_icons
플레이스토어(구글), 앱스토어(애플)에서 각각 앱들은 고유한 파일이름 같은게 필요
Android 에서는 패키지명, iOS에서는 Bundle ID(Bundle Identifier)
| 항목 | Android (패키지명) | iOS (번들 ID) |
|---|---|---|
| 명칭 | Package Name | Bundle Identifier |
| 형식 | com.example.my_app | com.example.myApp |
| 스타일 | 스네이크 케이스 (소문자, 언더스코어 _) | 카멜 케이스 (대소문자 혼용) |
| 구조 | 도메인 거꾸로 + 앱 이름 | 도메인 거꾸로 + 앱 이름 |
보통 웹사이트 주소(도메인): www.sparta.com
이걸 앱 식별자에 적용할 때는 도메인을 거꾸로 씀: com.sparta
여기에 앱 이름을 붙이면:
Android: com.sparta.todo_app
iOS: com.sparta.todoApp
XCode 에서 Bundle Identifier 수정
쉽게 할 수 있는 패키지가 있음
dart run change_app_package_name:main com.sparta.todo_app --androiddart run change_app_package_name:main com.sparta.todoApp --iosFirebase 웹 콘솔에는 Android, iOS 각 식별자 별로 앱이 등록
앱 식별자가 바뀌었으니 Firebase 에도 등록해주어야하는데 등록 후 구성파일도 최신화 해주어야 함
.apk 파일 생성.aab 파일 생성aab 로 빌드여기까지만 듣고 dart 기초 강의부터 다시 듣기로 했다
Software Development Kit
Dart 코드를 작성 하고,
빌드 (Build) 하고,
디버깅 (Debugging) 하고,
테스트 (Test) 할 때 필요한 모든 도구를 제공
Stable, Beta, Dev 세 가지 버전이 존재
3개월마다 업데이트되는 안정적인 배포 버전이며, x.y.z의 숫자 형태로 표시되고, 대부분의 개발에서 추천
한 달에 한 번 배포되며, 새로운 기능을 Stable에 넣기 전 미리 시험해보는 단계라서 안정적이지 않다, 버전 표기는 x.y.z+a.b beta 형태
2주마다 배포되고 최신 미검증 기능들이 포함돼서 안정적이지 않으나, 기능 테스트 목적이라면 사용할 수 있다, 버전은 x.y.z+a.b dev처럼 표기된다.
웹 브라우저에서 Dart 코드를 작성하고, 바로 실행할 수 있는 온라인 코드 편집기
따로 개발 환경을 세팅하지 않아도 사용 가능하다는 장점이 있다
변수는 데이터를 저장하는 공간 (데이터를 담는 상자)
변수는 컴퓨터의 메모리 공간 어딘가에 데이터를 저장, 필요할 때 변수 이름을 통해 해당 데이터를 꺼내 씀
⭕️ 반드시 이름 붙이기
❌ 중복 이름 -> 오류 발생
타입 변수 이름 = 값int age = 30;
String name = 'Bob';
bool isStudent = true;
변수 이름 = 값;var name = 'Bob';
-> 타입 추론 변수
타입 변수 이름 = 값;final firstCurrentTime = DateTime.now(); // 타입 명시하지 않은 경우
final DateTime secondCurrentTime = DateTime.now(); // 타입 명시한 경우
변수 이름 = 값;const pi = 3.14159; // 타입 명시하지 않은 경우
const int age = 30; // 타입 명시한 경우
| 구분 | final | const |
|---|---|---|
| 초기화 시점 | 런타임(실행 중) | 컴파일 타임(빌드 시) |
| 값 변경 가능 여부 | 변경 불가 | 변경 불가 |
| 값 결정 시점 | 실행하면서 값이 정해져도 됨 | 실행 전부터 값이 100% 확정되어 있어야 함 |
| 대표 예시 | 현재 시간(DateTime.now), API로 받아오는 값 | 원주율, 앱 전체에서 고정된 문자열/숫자 |
| 메모리 특성 | 매번 인스턴스 생성 가능 | 동일한 const 객체는 앱 전체에서 재사용됨(캐싱) |
| 항목 | 내용 | 예시 |
|---|---|---|
| 의미 | 나중에 초기화할 변수를 미리 선언할 때 사용 | late int a; |
| 초기화 시점 | 선언 시 바로 초기화하지 않고, 사용 전에 값을 할당 | a = 10; |
| 용도 | 초기값이 바로 정해지지 않거나, 나중에 할당해야 하는 경우 | 클래스 멤버 변수, 계산 후 값 할당 등 |
| 주의점 | 초기화 전에 사용하면 런타임 에러 발생 | print(a); // 초기화 전이면 오류 |
late를 쓰면 null을 허용하지 않으면서 초기화를 늦출 수 있음
late final과 같이 쓰면, 한 번만 나중에 초기화 가능
소수점 없는 숫자만 저장
메모리적으로 double보다 단순
소수점 있는 숫자 저장
정수를 넣으면 자동으로 1 → 1.0처럼 변환
부동소수점 연산 특성 때문에 아주 큰 숫자나 아주 작은 소수에서는 오차가 생길 수 있음
int + double을 모두 담을 수 있음
필요할 때 자동으로 double로 변환될 수 있음
→ ex. num x = 1; x += 2.5; // double로 바뀜
int나 double로 타입을 고정하기 애매한 값에 사용
| 타입 | 의미 | 특징 | 예시 |
|---|---|---|---|
| int | 정수 타입(소수점 없음) | 음수·양수 모두 가능 / 소수 불가 | int a = 1;int b = -5; |
| double | 실수 타입(소수점 있음) | 정수를 넣어도 자동으로 .0 붙음 | double a = 1.5;double b = 0.1234;double z = 1; // 1.0 |
| num | 숫자 전체(int + double) | int, double 둘 다 저장 가능 / 연산 시 타입이 변할 수도 있음 | num a = 1;a += 2.5; // 3.5 |
변수 그대로 넣고 싶을 때: $name
함수 호출하거나 계산이 필요할 때: ${name.toUpperCase()}
== 으로 내용이 같으면 true
→ 객체 비교가 아니라 “값” 비교
HTML 텍스트, 긴 설명, JSON 같은 거 그대로 넣기 좋음
개행(줄바꿈)도 그대로 유지됨
| 항목 | 내용 | 예시 |
|---|---|---|
| 타입 | String | String a = 'hello'; |
| 따옴표 | 작은따옴표 ' ' / 큰따옴표 " " 모두 가능 | String b = "bye"; |
| 문자열 보간(String interpolation) | 변수: $변수 / 표현식: ${표현식} | 'Hello, $name''Hi, ${name.toUpperCase()}' |
| 비교 | == 으로 문자열 동일 비교 가능 | 'a' == 'a' |
| 문자열 합치기 | + 로 연결 | 'Hello ' + 'World' |
| 대소문자 변환 | toUpperCase() / toLowerCase() | name.toUpperCase() |
| 여러 줄 문자열 | ''' ... ''' 또는 """ ... """ | 여러 줄 텍스트 작성 가능 |
| 항목 | 내용 | 예시 |
|---|---|---|
| 타입 | bool | bool isLogin = true; |
| 역할 | 참/거짓 판단에 사용 | 조건문, 반복문에서 주로 사용 |
| 값 종류 | true, false | bool isEmpty = false; |
| 사용 예 | 조건 체크 | if (isLogin) { ... } |
| 항목 | 내용 | 예시 |
|---|---|---|
| 의미 | 값이 “없음”을 표현 | null |
| Nullable 변수 | 타입 뒤에 ? 붙임 | int? b = null; |
| Non-nullable 변수 | ? 없음 → null 불가 | int a = 3; |
| 초기값 | Nullable 변수는 초기값이 없으면 자동으로 null | String? name; // null |
| 주의점 | null 상태에서 사용하면 오류 발생 가능 → 최소한으로 사용 권장 | name!.length 같은 null 강제 사용은 위험 |
| 연산자 | 의미 | 설명 | 예시 |
|---|---|---|---|
| + | 덧셈 | 두 값을 더함 | 2 + 3 → 5 |
| - | 뺄셈 | 앞의 값에서 뒤의 값을 뺌 | 3 - 2 → 1 |
| * | 곱셈 | 두 값을 곱함 | 2 * 3 → 6 |
| / | 나눗셈 | 결과를 double로 반환 | 5 / 2 → 2.5 |
| ~/ | 몫 | 나눈 결과의 정수 부분만 반환 | 5 ~/ 2 → 2 |
| % | 나머지 | 나눗셈 후 남는 나머지 반환 | 6 % 4 → 2 |
| 연산자 | 의미 | 반환값 조건 | 예시 |
|---|---|---|---|
| == | 같다 | 값이 같으면 true | 4 == 4 → true |
| != | 다르다 | 값이 다르면 true | 2 != 4 → true |
| >, < | 초과, 미만 | 왼쪽, 오른쪽 값이 더 크면 true | 4 > 2 → true |
| >=, <= | 이상, 이하 | 왼쪽, 오른쪽 값이 크거나 같으면 true | 3 >= 3 → true |
| 연산자 | 역할 | 설명 | 예시 |
|---|---|---|---|
| as | 타입 변환 | 형변환이 가능할 때만 사용 | a as double |
| is | 타입 확인 | 해당 타입이면 true | a is int |
| is! | 타입 부정 확인 | 해당 타입이 아니면 true | a is! int |
| 연산자 | 의미 | 설명 |
|---|---|---|
| = | 기본 대입 | 오른쪽 값을 왼쪽 변수에 넣음 |
| ??= | null 일 때만 대입 | 왼쪽이 null이면 오른쪽 값 대입 |
| += | 더한 후 대입 | a = a + b와 동일 |
| -= | 뺀 후 대입 | a = a - b와 동일 |
| *= | 곱한 후 대입 | a = a * b와 동일 |
| /= | 나눈 후 대입 | a = a / b와 동일 |
| ~/= | 나눈 정수 결과 대입 | a = a ~/ b와 동일 |
| %= | 나머지를 대입 | a = a % b와 동일 |
참/거짓을 반전
true → false, false → true
둘 중 하나라도 true면 true
둘 다 false일 때만 false
둘 다 true일 때만 true
하나라도 false면 false
조건이 true일 때만 실행
조건은 반드시 true/false 로 판별 가능해야 함
int a = -1;
if (a < 0) {
print('a 는 음수입니다.');
}
앞의 if 조건이 false일 때 다음 조건을 검사
여러 조건을 순서대로 체크할 때 사용
int score = 75;
if (score >= 90) {
print('A');
} else if (score >= 80) {
print('B');
} else if (score >= 70) {
print('C');
}
위 조건들이 모두 false일 때 실행
조건 없음
int score = 50;
if (score >= 90) {
print('A');
} else {
print('점수가 낮아요');
}
하나의 값에 대해 가능한 경우들을 케이스별로 나열해야 할 때
값 비교가 많아지면 if문보다 가독성 좋음
switch (값) {
case 값1:
...
break;
case 값2:
...
break;
default:
...
}
int dayOfWeek = 1;
switch (dayOfWeek) {
case 1:
print('월요일');
break;
case 2:
print('화요일');
break;
case 3:
print('수요일');
break;
default:
print('유효하지 않은 숫자');
}
조건 ? 값1 : 값2
조건이 true → 값1
조건이 false → 값2
표현식1 ?? 표현식2
표현식1이 null이 아니면 → 표현식1
표현식1이 null이면 → 표현식2
int? a = null;
print(a ?? 2); // 2
int b = 3;
print(b ?? 2); // 3
반복문은 같은 코드를 여러 번 반복해서 실행할 때 사용
같은 코드를 여러 번 쓰는 대신 반복문을 사용하면 효율적이고 코드가 깔끔해짐
반복 횟수가 명확할 때
for (초기화식; 조건식; 증감식) { … }
초기화식에서 변수를 선언, 조건식이 true이면 코드 블록 실행
증감식 실행 후 다시 조건식 검사, 조건식이 false면 반복 종료
for (var i = 0; i < 5; i++) {
print(i); // 0 1 2 3 4
}
초기화식 변수는 반복문 안에서 사용 가능. 상수(final)로 선언하면 안 됨
반복 횟수가 명확하지 않고 조건에 따라 반복해야 할 때.
while (조건식) { … }
조건식 검사, 조건이 true이면 코드 블록 실행, 조건이 false이면 반복 종료
int count = 1;
while (count <= 5) {
print(count);
count += 1;
}
코드 블록을 최소 1회는 실행하고 싶을 때
do {// 실행 코드} while (조건식);
먼저 코드 블록 1회 실행, 조건식이 true이면 반복 계속, 조건식이 false이면 반복 종료
int count = 6;
do {
print(count); // 6
count++;
} while (count <= 5);
조건식이 false여도 최소 1회는 실행됨
리스트나 집합 같은 컬렉션(List, Set, ㅇㅇMap) 요소를 순회할 때
for (var item in 컬렉션) { … }
컬렉션 요소를 순서대로 변수에 대입하며 반복
List<String> fruits = ['사과', '바나나', '귤'];
for (var fruit in fruits) {
print(fruit);
}
반복문을 즉시 종료
for (var i = 1; i <= 5; i++) {
if (i == 3) break;
print(i); // 1 2
}
이번 반복만 건너뛰고 다음 반복 진행
for (var i = 1; i <= 5; i++) {
if (i == 3) continue;
print(i); // 1 2 4 5
}
여러 개의 값을 묶어서 효율적으로 관리할 수 있는 자료형
순서가 있는 값들의 묶음 (배열)
List<타입> 변수명 = [요소1, 요소2, ...];
List<int> numbers = [1, 2, 3, 4, 5];
List<int> numbers = [];
var names = <String>[];
var numbers = [1, 2, 3]; // 변경 가능
final names = ['Alice', 'Bob']; // 변경 불가
List<String> fruits = ['사과', '오렌지', 8]; // ❌ 오류
const fruits = ['사과', '오렌지', 8];
print(fruits.runtimeType); // List<Object>
var numbers = [1, 2, 3];
print(numbers[0]); // 1
numbers[0] = 5;
print(numbers[0]); // 5
print(numbers.length); // 3
print(numbers.isEmpty); // false
print(numbers.isNotEmpty); // true
print(numbers.indexOf(2)); // 1
print(numbers.indexOf(9)); // -1
var fruits = ['사과', '오렌지'];
fruits.add('바나나'); // ['사과', '오렌지', '바나나']
fruits.addAll(['포도', '귤']); // ['사과', '오렌지', '바나나', '포도', '귤']
fruits.remove('오렌지'); // ['사과', '바나나', '포도', '귤']
fruits.removeAt(0); // ['바나나', '포도', '귤']
fruits.clear(); // []
순서가 없고 중복되지 않은 값들의 묶음
Set<타입> 변수명 = {요소1, 요소2, ...};
Set<int> numbers = {1, 2, 3, 4, 5};
Set<int> numbers = {};
var names = <String>{};
var numbers = {1, 2, 3}; // 변경 가능
final names = {'Alice', 'Bob'}; // 변경 불가
var names = {}; // Map 타입으로 추론됨
Set<String> fruits = {'사과', 8}; // ❌ 오류
var fruits = {'사과', 8};
print(fruits.runtimeType); // _HashSet<Object>
dartvar fruits = {'사과', '사과', '오렌지'};
print(fruits); // {'사과', '오렌지'}
print(fruits.length); // 2
print(fruits.isEmpty); // false
print(fruits.isNotEmpty); // true
fruits.add('바나나'); // {'사과', '오렌지', '바나나'}
fruits.addAll({'포도', '귤'}); // {'사과', '오렌지', '바나나', '포도', '귤'}
remove(): 값으로 삭제
fruits.remove('오렌지'); // {'사과', '바나나', '포도', '귤'}
Index가 없으므로 removeAt() ✘
키(Key)와 값(Value)의 쌍으로 이루어진 컬렉션
Map<키타입, 값타입> 변수명 = {키1: 값1, 키2: 값2, ...};
Map<String, String> people = {'Alice': 'Teacher', 'Bob': 'Student'};
Map<String, String> people = {};
var emptyMap = <String, int>{};
var people = {'Alice': 'Teacher'};
final animals = {'Dog': 3, 'Cat': 5};
var people = {'Alice': 25, 45: 'Bob'};
print(people.runtimeType); // LinkedMap<Object, Object>
Map<String, int> people = {'Alice': 25, 'Bob': 25, 'Alice': 23};
print(people); // {Alice: 23, Bob: 25}
print(people['Alice']); // 25
people['Alice'] = 28; // 기존 키 값 수정
people['Charlie'] = 35; // 새로운 키-값 추가
people.remove('Bob');
print(people); // {Alice: 28, Charlie: 35}
people.containsKey('Alice'); // true
people.keys; // (Alice, Charlie)
people.values; // (28, 35)
| 컬렉션 | 순서 | Index | 중복 허용 |
|---|---|---|---|
| List | O | O | O |
| Set | X | X | X |
| Map | 키 순서 있음 | 키로 접근 | 키 중복 불가, 값 중복 가능 |
여러 개의 고정된 상수 값들을 묶어놓은 타입
대표적으로 상태값, 옵션값, 색상, 카테고리 등에 사용
enum Color { red, green, blue }
enum Animal {
cat,
dog,
tiger,
elephant
}
var myColor = Color.blue;
switch (myColor) {
case Color.red:
case Color.green:
case Color.blue:
}
Color.red.index; // 0
Color.values; // [Color.red, Color.green, Color.blue]
Color.red.name; // red
값 추가/삭제 가능 여부
Set: 가능 (add, remove)
enum: 불가능 (정의 후 변경 불가)
중복 허용 여부
Set: 중복 넣어도 오류 없음
enum: 중복 이름 사용 시 오류
순서
Set: 순서 없음
enum: 순서 있음 (index 존재)
정해진 선택지를 코드에서 강제로 고정하기 위해
실수로 이상한 값을 넣지 못하게 막아줌
예: Status { idle, running, error }
→ 이 세 개 외 값은 절대 못 들어감
코드의 의미를 더 명확하게 만들기 위해
숫자나 문자열 대신 의미 있는 이름으로 표현 가능
0, 1, 2 대신 Status.idle, Status.running처럼 확실함
타입 안정성(Type Safety)
컴파일 단계에서 잘못된 값 사용을 바로 잡아줌
문자열로 상태 관리하면 오타 나도 런타임까지 모름
switch문과 함께 쓰면 상태 분기 처리하기 편함
모든 경우를 강제로 처리하게 해서 버그 줄임
정해진 옵션이 바뀌지 않는 경우 관리가 쉬움
앱의 상태, 카테고리, 버튼 타입, 화면 모드처럼 “정해진 목록”에 딱 맞음
변하지 않는 고정된 선택지를 안전하게 관리하기 위해!!
오늘은 사전 캠프 때 배웠던 Dart 문법을 기초부터 다시 시작했다.
예전에 배웠던 자바 스크립트랑 비슷한 부분이 있어 비교하면서 또 들었다.
Dart 과제는 튜터님이 낸 과제 풀기인데 언능 강의 듣구 과제 풀어보고 싶다 ㅎㅁㅎ/
이번에 공부했던 것중에 흥미로운 건 Enum같다. 이넘시끼..
강의만 들었을 때는 그래서 왜 사용하는 걸까? 궁금했는데 튜터님께 찾아가니 어느정도 이해 된 것 같다
일단 내가 생각하기에 enum을 쓰는 이유는 문자열로 사용했을 때 런타임 전까지 오류인 걸 모를 수도 있는데 enum을 사용하면 vscode에서 자동완성도 해주고 오류 값으로 보여주기 때문에 안정성을 높이려고 사용하는 걸로 이해했다.