
공식 문서
자식 위젯의 투명도를 조절하는 위젯
opacity 값(0.0 ~ 1.0)으로 투명도 지정
1.0 = 완전 불투명
0.0 = 완전 투명
위젯 자체를 정말로 “투명하게” 보여야 할 때만
단순히 사라지는 애니메이션이면 AnimatedOpacity가 더 자연스럽고 권장
Opacity를 애니메이션으로 부드럽게 변화시키는 위젯
opacity 값이 바뀌면 지정한 duration 동안 자동으로 애니메이션 실행
상태 변화에 반응하는 위젯이라 StatefulWidget 안에서 사용하는 경우가 많음
공식 문서
비동기 작업(Future)의 상태 변화에 따라 UI를 자동으로 바꿔주는 위젯
예: 서버 요청, 파일 로딩, DB 읽기 같은 작업 처리할 때 사용
future: 실행할 비동기 함수.
builder: Future의 상태에 따라 어떤 위젯을 그릴지 결정하는 함수
FutureBuilder(
future: fetchData(), // Future 함수
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // 로딩 상태
}
if (snapshot.hasError) {
return Text('에러 발생');
}
if (snapshot.hasData) {
return Text(snapshot.data.toString()); // 데이터 표시
}
return SizedBox(); // 기본값
},
);
AsyncSnapshot의 비동기 상태를 나타내는 열거형 값
보통 FutureBuilder/StreamBuilder의 builder에서 먼저 확인함
ConnectionState.none
future 아직 연결(할당) 되지 않은 상태.
FutureBuilder에서는 future == null일 때 주로 나옴.
ConnectionState.waiting
Future가 실행(요청) 되어 결과를 기다리는 중(로딩 상태)
로딩 인디케이터 표시할 때 사용
ConnectionState.done
Future가 완료되었거나(성공/에러), 스트림이 종료된 상태.
이 시점에서 snapshot.data 또는 snapshot.error 확인.
snapshot.hasError: 에러가 있는지 여부 (snapshot.error != null)snapshot.error: 실제 에러 객체. 에러 메시지나 타입 확인 가능snapshot.hasData: 데이터가 있는지 여부. (내부적으로 snapshot.data != null 체크)
즉, data가 null이면 hasData는 false
snapshot.data: Future가 반환한 실제 값
특정 기능을 쉽게 사용할 수 있도록 미리 작성된 코드 묶음
크게 Dart SDK에 포함된 라이브러리 / 외부 라이브러리(pub.dev) 로 나뉨
dart:core
기본 타입(int, double, String), 컬렉션 등 핵심 기능
자동 포함 → import 불필요
dart:async
Future, Stream 등 비동기 기능
dart:collection
추가 컬렉션 타입(Queue, Linked List, HashMap 등)
dart:convert
JSON, UTF-8 인코딩/디코딩
dart:developer
디버거와 상호작용
dart:math
수학 함수, 상수, 랜덤
dart:ffi
Dart에서 C API 사용
dart:io
파일, 소켓, HTTP 등 입출력 기능
package:web
가벼운 브라우저 API 연결
dart:js_interop
JS 및 브라우저 API 상호운용
dart:html
Web 요소 및 리소스 제어
외부 개발자가 만든 라이브러리
https://pub.dev 에서 검색 가능
cupertino_icons — Flutter Cupertino 기본 아이콘
intl — 날짜/숫자 포맷, 국제화
shared_preferences — 간단한 로컬 저장소
url_launcher — URL 실행
image_picker — 카메라·앨범 접근
firebase_core — Firebase 연동 기본
firebase_auth — Firebase 인증
google_fonts — 구글 폰트
permission_handler — 권한 처리
flutter_svg — SVG 렌더링
cached_network_image — 이미지 캐싱
flutter_local_notifications — 로컬 알림
path_provider — 파일 시스템 접근
geolocator — 위치 서비스
dio — HTTP 네트워크 라이브러리
custom_lint — Lint 규칙 설정
기본
import 'dart:html';
import 'package:http/http.dart';
import 'src/my_utils.dart';
별칭 지정 — as
import 'package:http/http.dart' as http;
필요한 것만 가져오기 — show
import 'package:lib1/lib1.dart' show foo;
특정 것 제외 — hide
import 'package:lib2/lib2.dart' hide foo;
필요한 시점에 라이브러리를 불러오는 방식
→ Web에서만 지원
지연 로딩 - deferred as
import 'package:greetings/hello.dart' deferred as hello;
호출
Future<void> greet() async {
await hello.loadLibrary();
hello.printGreeting();
}
loadLibrary() 는 deferred as 를 사용하면 자동으로 제공
순차 실행
앞 작업이 끝나야 다음 작업 실행됨
시간이 오래 걸리는 작업에서 프로그램 전체가 멈춤
기다리는 작업이 있어도 전체 코드 흐름을 멈추지 않는 방식
비동기 작업 1개의 결과를 “한 번만” 반환하는 클래스.
Future<T> 형태 → 제네릭
즉시 값 만들기
Future<int> a = Future.value(1);
1) 지연만 발생
Future.delayed(Duration(seconds: 2));
2) 지연 후 실행할 코드 전달
Future.delayed(Duration(seconds: 2), () {
print('2초 뒤 실행');
});
void introduce(String name) {
print('$name 시작');
Future.delayed(Duration(seconds: 2), () {
print('안녕 나는 $name');
});
print('$name 끝');
}
→ 끝이 먼저 찍히고, 나중에 delayed 내용 실행
await 로 순서 보장
void introduce(String name) async {
print('$name 시작');
await Future.delayed(Duration(seconds: 2), () {
print('안녕 나는 $name');
});
print('$name 끝');
}
여러 작업을 순서대로 실행
void main() async {
await introduce('A');
await introduce('B');
}
introduce(A)가 끝나야 introduce(B) 실행
이때 introduce도 future로 감싸야함
future <void> introduce(String name) async {}
Future<String> introduce(String name) async {
await Future.delayed(Duration(seconds: 2));
return '이름은 $name';
}
void main() async {
var a = await introduce('A');
var b = await introduce('B');
print('$a / $b');
}
시간 순서대로 여러 개의 값을 비동기적으로 방출
이벤트 여러 번 발생할 때 사용
Stream<T> 형태 → 제네릭
값을 하나씩 방출하는 키워드
Stream<String> names() async* {
yield 'A';
yield 'B';
yield 'C';
}
Stream에서 방출되는 값을 실시간으로 받음
names().listen((value) {
print(value);
});
Stream<String> emitNames(List<String> names) async* {
for (var i = 0; i < names.length; i++) {
yield '${i+1}번째 ${names[i]}';
}
}
10 → 0 방출
Stream<int> emitNumbers(int first) async* {
for (var i = first; i >= 0; i--) {
yield i;
}
}
비동기임을 확인
print('시작');
emitNumbers(10).listen(print);
print('끝');
“끝”이 먼저 출력됨 → 비동기
1초 간격으로 숫자 방출
Stream<int> emitNumbers(int first) async* {
for (var i = first; i >= 0; i--) {
yield i;
await Future.delayed(Duration(seconds: 1));
}
}
동시에 두 개 실행하면?
emitNumbers(10).listen((n) => print('타이머1 $n'));
emitNumbers(10).listen((n) => print('타이머2 $n'));
→ 두 Stream이 동시에 독립적으로 동작
Future
결과 1번
async/await 사용
네트워크 요청, 파일 읽기 같은 단발성 작업에 적합
Stream
결과 여러 번
listen()으로 실시간 구독
버튼 클릭, 센서 데이터, 타이머 등 지속적 이벤트에 적합
Dart 공식 문서 - Map
Dart Programming Tutorial
개인 과제를 진행하다가 Map 키랑 값 사용하는 부분이 궁금해서 찾아봤다.
예: { 'name': '현서', 'age': 24 } — 'name'이 키, '현서'가 값.
키로 값을 조회하고(map['name']), 키로 값을 변경하거나 추가(map['city'] = 'Seoul')할 수 있음
var m = {'a': 1, 'b': 2};
m['a'] = 3; // {'a':3, 'b':2}
var m2 = {'a':1, 'a':4}; // 실제 리터럴에서는 마지막 값만 남음 -> {'a':4}
여러 값을 저장하려면 값 쪽을 리스트나 다른 컬렉션으로 만들어야 함 ('a': [1,2])
Iterable = for문으로 돌릴 수 있는 자료 구조
List, Set, Map의 keys, Map의 values, Map의 entries
이런 것들이 모두 Iterable
for (var k in map.keys) print(k);
for (var v in map.values) print(v);
for (var e in map.entries) print('${e.key}: ${e.value}');
ConcurrentModificationError 같은 문제가 발생var m = {'a':1, 'b':2};
for (var k in m.keys) {
if (k == 'a') m.remove('b'); // 잘못된 사용 -> 에러 발생 가능
}
Iterable<MapEntry<K, V>> 형태
키와 값을 한 쌍으로 묶은 MapEntry들을 반복해서 읽을 수 있음
읽기 전용이지만, 내부 값은 수정할 수 있음
var map = {'a': 1, 'b': 2};
for (var entry in map.entries) {
print('${entry.key}: ${entry.value}');
}
직접 map.entries.add() 불가능
객체의 해시값
Map 그 자체의 해시코드이며, 키나 값의 해시코드와는 별개
주로 Map이 동일한 객체인지 비교에 사용됨
읽기 전용
맵이 비어 있으면 true / !isEmpty
var m = {};
print(m.isEmpty); // true
var m = {'a': 1};
print(m.isNotEmpty); // true
Iterable<K>
키만 모아둔 반복 가능한 객체
읽기 전용 (추가/삭제 불가)
var map = {'a':1, 'b':2};
for (var k in map.keys) print(k);
Iterable<V>
값만 모아서 반복해서 읽을 수 있음
읽기 전용
for (var v in map.values) print(v);
Map 안의 key/value 쌍의 개수
var m = {'a':1, 'b':2};
print(m.length); // 2
런타임에 어떤 타입의 Map인지 알려줌
읽기 전용
print({'a':1}.runtimeType);
// _Map<String, int>
| 속성 | 설명 |
|---|---|
entries | MapEntry 반복자, 읽기 전용 |
hashCode | 해시 코드, 읽기 전용 |
isEmpty | 맵이 비어있는지 여부 |
isNotEmpty | 맵에 값이 있는지 여부 |
keys | 키 반복자, 읽기 전용 |
values | 값 반복자, 읽기 전용 |
length | 키/값 쌍의 개수 |
runtimeType | 객체의 런타임 타입, 읽기 전용 |
다른 Map의 모든 key/value를 현재 Map에 추가
같은 키가 있으면 기존 값을 덮어씀
map.addAll({'b': 20, 'c': 30});
여러 MapEntry 객체를 한 번에 추가
entries는 map.entries 형태처럼 key/value 묶음
map.addEntries([MapEntry('x', 10), MapEntry('y', 20)]);
Map을 다른 타입(Map<RK, RV>)으로 보는(view) 기능
실제 데이터를 바꾸지는 않음
타이프 캐스팅 오류는 사용 시점에 발생함
Map<dynamic, dynamic> m = {'a': 1};
Map<String, int> m2 = m.cast<String, int>();
모든 key/value 삭제 → 비어 있는 Map이 됨
map.clear();
특정 키가 Map에 존재하는지 확인
map.containsKey('a'); // true/false
특정 값이 있는지 확인
map.containsValue(100); // true/false
모든 key/value에 대해 콜백 실행
반환값 없음
map.forEach((k, v) => print('$k: $v'));
각 엔트리를 변환해서 새로운 Map을 생성
원본 Map을 수정하지 않음
var result = map.map((k, v) => MapEntry(k.toUpperCase(), v * 2));
키가 없을 때만 값을 추가
키가 이미 있으면 아무것도 안 함
특정 키 삭제
삭제된 값(value)을 반환
키 없으면 null 반환
map.remove('a');
조건(test)에 맞는 key/value 모두 삭제
map.removeWhere((k, v) => v < 10);
특정 키가 있을 때 값 업데이트
키가 없다면 ifAbsent가 있으면 새로 추가
map.update('a', (v) => v + 1);
map.update('b', (v) => v, ifAbsent: () => 100);
Map의 모든 value를 업데이트.
map.updateAll((k, v) => v * 2);
| 메서드 종류 | 대표 메서드 | 특징 |
|---|---|---|
| 추가 | addAll, addEntries, putIfAbsent | 키 중복 처리 다름 |
| 삭제 | remove, removeWhere, clear | 부분 삭제/전체 삭제 |
| 조회 | containsKey, containsValue | 존재 판단 |
| 변환 | map, cast | 새 Map 생성 / 타입 view |
| 업데이트 | update, updateAll | 특정 값 혹은 전체 값 변경 |
| 순회 | forEach | 읽기만 가능 |
수학 상수, 함수들, 랜덤 생성기를 제공
import 'dart:math';
| 상수 | 의미 |
|---|---|
pi | 원주율 π ≈ 3.14159 |
e | 자연상수 e ≈ 2.71828 |
sqrt2 | √2 |
sqrt1_2 | 1/√2 |
ln10 | ln(10) |
log2e | log₂(e) |
log10e | log₁₀(e) |
| 함수 | 설명 |
|---|---|
sqrt(x) | x의 제곱근 |
pow(x, y) | x의 y제곱 |
sin(x) | x 라디안의 사인 |
cos(x) | x 라디안의 코사인 |
tan(x) | x 라디안의 탄젠트 |
asin(x) | 사인 역함수 (라디안) |
acos(x) | 코사인 역함수 (라디안) |
atan(x) | 탄젠트 역함수 (라디안) |
atan2(y, x) | y/x 의 역탄젠트, 사분면 정보 포함 |
log(x) | 자연로그 (ln) |
exp(x) | e^x |
min(a, b) | a와 b 중 작은 값 |
max(a, b) | a와 b 중 큰 값 |
abs(x) | x의 절댓값 |
clamp(x, lower, upper) | lower ≤ x ≤ upper 로 제한 |
숫자, 불린 랜덤값 생성
Random(seed) → 시드를 지정하면 항상 같은 난수 시퀀스를 생성할 수 있음.
nextInt(max) → 0부터 max-1 사이 정수
nextDouble() → 0.0 ~ 1.0 사이 실수
nextBool() → true/false 랜덤
Point 클래스: 2D 좌표를 표현할 때 사용
Rectangle 클래스: 사각형 영역 표현
Vector 관련 기능은 별도 라이브러리 필요
2D 좌표 표시용 클래스
const Point(0, 0);
const Point(200, 400);
좌상단(left, top)과 우하단(right, bottom)으로 직사각형 생성
Rectangle.fromPoints(Point(20, 50), Point(300, 600));
또는 (left, top, width, height)로 선언
const Rectangle(20, 50, 300, 600);
너비, 높이 수정 가능
var rect = MutableRectangle(20, 50, 300, 600);
rect.width = 200;
rect.height = 100;
사실 무슨 소리인지 어떻게 써야할지는 잘 모르겠지만..
랜덤값 출력하려고 뜯어 봤다,.
개인 과제로 Dart를 이용해 점수에 따른 등급 계산, 장바구니 계산, 로또 발급 프로그램을 만들어봤다. 장바구니 총 금액을 계산할 때 장바구니에 담긴 상품 리스트(List)와 상품별 가격표(Map)를 연결해서 합계를 구하는 방법을 처음에는 어떻게 구현해야 할지 감이 잡히지 않았다. 어떤 반복문을 사용할지, Map에서 값을 가져올 때 null 안전성을 어떻게 보장해야 하는지 고민이 많았다. 그래서 Dart 공식 문서를 참고하며 Map의 다양한 속성과 메서드를 분석했고 priceList[item]!처럼 값을 가져오는 방식으로 문제를 해결했다. 이 과정을 통해 Map과 List를 함께 활용하는 패턴을 익힐 수 있었고, 컬렉션 자료형에 대한 이해가 많이 깊어졌다.
또한 프로그램에서 어떤 상황에서 예외가 발생할지 고민하는 과정도 의미 있었다. 장바구니가 비어 있을 때, 존재하지 않는 상품이 포함되었을 때, 점수가 0~100 범위를 벗어났을 때 등 다양한 예외 상황을 생각하며 만들었다. 단순히 주어진 코드를 구현하는 것이 아니라 사용자가 잘못된 입력을 했을 때 프로그램이 안전하게 동작하도록 하는 방법을 고민하면서 예외 처리의 중요성을 다시 한 번 느낄 수 있었다.
이번 과제를 통해 컬렉션 활용, 반복문, 조건문, 예외 처리, 랜덤 데이터 처리 등 Dart에서 자주 쓰이는 기능들을 실제 상황에 적용해 보는 연습을 충분히 할 수 있었다. 또한 문제를 분석하고, 공식 문서를 참고하며 해결 방안을 찾는 과정에서 프로그래밍 문제 해결 능력도 한층 향상되었다고 느꼈다. 앞으로도 이렇게 하나씩 구현하면서 코드를 점점 더 안전하고 효율적으로 작성하는 연습을 계속해 나가고 싶다.