Dart 튜토리얼 13편: 파일·네트워크 I/O(dart:io·HTTP fetch)
요약
핵심 요지
- 문제 정의: 파일을 읽거나 네트워크 요청을 보내는 코드는 결과가 “바로” 오지 않아서, 동기식으로 만들면 앱이 멈춘 것처럼 보이거나(블로킹) 에러 처리가 누락되기 쉽다.
- 핵심 주장:
dart:io1의 I/O는Future2/Stream3 기반 비동기로 다루고, HTTP 요청은 플랫폼 종속인HttpClient4보다package:http5 같은 상위 라이브러리로 처리하는 흐름이 기준이 된다. - 주요 근거: 파일은
readAsString()6/readAsLines()7/openRead()8, 출력은openWrite()9, 네트워크는Uri10 +http.read()11/http.get()12 같은 예시로 구성된다. - 실무 기준: 작은 파일은 “한 번에 읽기”, 큰 파일은 “스트리밍으로 줄 단위 처리”, HTTP는 “URL 만들기 → 요청 →
statusCode13 확인 →body14 사용”을 기본 틀로 둔다.
문서가 설명하는 범위
dart:io에서 파일을 텍스트/바이너리로 읽고, 스트림으로 처리하고, 파일에 쓰는 방법dart:io의 비동기 중심 설계(Future/Stream)와 에러 처리(try/catch) 흐름package:http로 URL(Uri)을 만들고, HTTP 요청을 보내고, 응답(Response15)을 다루는 기본 패턴
읽는 시간: 18분 | 난이도: 초급
참고 자료
- dart
- 파일/디렉터리/HTTP 서버·클라이언트 등 I/O API - Fetch data from the internet -
package:http로 HTTP 요청 + JSON 처리 흐름
문제 상황
I/O 작업은 결과가 “나중에” 도착합니다.
파일을 읽거나 네트워크 요청을 보내는 순간, 앱은 기다리는 동안에도 다른 일을 해야 합니다.
이 흐름을 동기식으로 만들면 코드가 멈춘 것처럼 보이거나, 에러가 실행 중에 터지고, 처리 순서가 꼬이기 쉽습니다.
그래서 I/O는 처음부터 비동기(Future/Stream) 패턴으로 익히는 것이 안전합니다.
해결 방법
단계 1: 작은 파일은 “한 번에 읽기”로 시작하기
Why
NOTE설정 파일처럼 작은 텍스트 파일은 전체를 한 번에 읽어도 부담이 적고, 로직을 단순하게 시작할 수 있습니다.
import 'dart:io';void main() async {var config = File('config.txt');// 파일을 문자열 하나로 읽는다.var stringContents = await config.readAsString();print('The file is ${stringContents.length} characters long.');// 파일을 “줄 단위 문자열 리스트”로 읽는다.var lines = await config.readAsLines();print('The file is ${lines.length} lines long.');}
What
NOTE
File16의readAsString()과readAsLines()는Future를 반환합니다.
즉, 파일을 읽는 동안에도 프로그램은 다른 작업을 진행할 수 있고, 결과는 “완료된 뒤”에 받게 됩니다.
How
TIP바이너리 파일(또는 텍스트라도 바이트로 다루고 싶을 때)은
readAsBytes()17로 한 번에 읽습니다.import 'dart:io';void main() async {var config = File('config.txt');var contents = await config.readAsBytes();print('The file is ${contents.length} bytes long.');}
Watch out
WARNING파일이 없거나 권한이 없으면 예외가 발생할 수 있습니다.
에러가 “잡히지 않은 예외”로 튀지 않게try/catch로 묶어둡니다.import 'dart:io';void main() async {var config = File('config.txt');try {var contents = await config.readAsString();print(contents);} catch (e) {print(e);}}
결론: 작은 파일은 “한 번에 읽기”로 시작하고, 항상 에러 처리를 함께 둡니다.
단계 2: 큰 파일은 Stream으로 “조금씩 읽기”로 전환하기
Why
NOTE파일이 크면 전체를 한 번에 메모리에 올리기 어렵습니다.
이때는openRead()로 바이트 스트림을 만들고, 처리 가능한 단위(예: 줄)로 잘라가며 읽는 편이 안전합니다.
What
NOTE
openRead()는Stream<List<int>>형태의 바이트 스트림을 반환합니다.
바이트를 텍스트로 바꾸려면utf8.decoder18와LineSplitter19 같은 변환기를 스트림에 적용합니다.
How
TIP바이트 스트림을 줄 단위 문자열 스트림으로 바꾼 뒤,
await for20로 하나씩 처리합니다.import 'dart:io';import 'dart:convert';void main() async {var config = File('config.txt');Stream<List<int>> inputStream = config.openRead();var lines = utf8.decoder.bind(inputStream).transform(const LineSplitter());try {await for (final line in lines) {print('Got ${line.length} characters from stream');}print('file is now closed');} catch (e) {print(e);}}
Watch out
WARNING
dart:io는 웹 앱에서는 사용할 수 없습니다.
웹이 아닌 Flutter 앱, 명령줄 스크립트, 서버에서만 사용할 수 있습니다.
결론: 큰 파일은 Stream으로 처리 단위를 정해 “조금씩” 처리합니다.
단계 3: 파일 출력은 IOSink로 “쓰기 → flush → close”까지 마무리하기
Why
NOTE로그처럼 “조금씩 계속 쓰는 출력”은 파일을 열고 반복해서 쓰는 흐름이 필요합니다.
이때openWrite()로IOSink21를 얻고, 마지막에 정리까지 해야 데이터가 안전하게 남습니다.
What
NOTE
openWrite()는 파일에 쓰기 위한IOSink를 제공합니다.
기본 모드는FileMode.write22로 기존 내용을 덮어쓰며, 이어쓰기라면FileMode.append23를 사용합니다.
How
TIP파일에 텍스트를 쓴 뒤
flush()24와close()25로 마무리합니다.import 'dart:io';Future<void> writeLog() async {var logFile = File('log.txt');var sink = logFile.openWrite();sink.write('FILE ACCESSED ${DateTime.now()}\n');await sink.flush();await sink.close();}이어쓰기 모드도 같은 방식으로 시작합니다.
var sink = logFile.openWrite(mode: FileMode.append);
Watch out
WARNING
close()를 호출하지 않으면, 버퍼에 남은 내용이 파일에 반영되지 않을 수 있습니다.
즉, “쓰기만 하고 끝”이 아니라, “마무리까지”를 코드로 고정해야 합니다.
결론: 출력은 IOSink로 쓰고, 마지막에 flush()/close()까지 끝냅니다.
단계 4: HTTP 요청은 Uri + package:http로 “요청 → 상태 코드 확인 → 본문 사용” 흐름을 고정하기
Why
NOTE네트워크 요청은 실패가 흔합니다.
성공/실패를 구분하지 않고 본문만 쓰면, 에러 상황에서 잘못된 값이 조용히 흘러갈 수 있습니다.
그래서 응답 전체(Response)를 받고,statusCode로 성공 여부를 먼저 확인하는 흐름이 필요합니다.
What
NOTE
package:http는 HTTP 요청을 위한 라이브러리입니다.
Uri로 URL을 만들고,http.get()같은 함수로 요청을 보낸 뒤,Response에서statusCode와body를 사용합니다.
How
TIP먼저 의존성을 추가하고(
dart pub add http), 가져온 데이터를 출력해 봅니다.Terminal window $ dart pub add httpimport 'package:http/http.dart' as http;void main() async {final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');final httpPackageResponse = await http.get(httpPackageUrl);if (httpPackageResponse.statusCode != 200) {print('Failed to retrieve the http package!');return;}print(httpPackageResponse.body);}필요하다면
headers를Map<String, String>형태로 함께 보낼 수 있습니다.await http.get(Uri.https('dart.dev', '/f/packages/http.json'),headers: {'User-Agent': '<product name>/<product-version>'},);
Watch out
WARNINGHTTP 요청은 시간이 걸릴 수 있으므로 비동기(
Future)로 동작합니다.
그리고 요청을 “여러 번” 보낼 때는Client26를 유지하고, 마지막에close()27로 정리하는 흐름이 제시됩니다.import 'package:http/http.dart' as http;void main() async {final httpPackageUrl = Uri.https('dart.dev', '/f/packages/http.json');final client = http.Client();try {final httpPackageInfo = await client.read(httpPackageUrl);print(httpPackageInfo);} finally {client.close();}}
결론: HTTP는 Uri로 URL을 만들고, 응답의 statusCode를 먼저 확인하는 흐름으로 시작합니다.
Footnotes
-
dart
(dart ): 파일/디렉터리/프로세스/소켓/HTTP 등 I/O API를 제공하는 라이브러리다. ↩ -
Future(퓨처): 나중에 완료되는 작업의 결과를 표현하는 타입이다. ↩
-
Stream(스트림): 시간이 지나면서 여러 이벤트/값이 들어오는 흐름을 표현하는 타입이다. ↩
-
HttpClient(HTTP 클라이언트):
dart:io에 있는 HTTP 클라이언트 클래스다. ↩ -
package
(package ): HTTP 요청을 위한 상위 수준 라이브러리다. ↩ -
readAsString()(문자열로 읽기): 파일 전체를 문자열로 읽어
Future<String>을 돌려주는 메서드다. ↩ -
readAsLines()(줄 단위로 읽기): 파일을 줄 단위 문자열 리스트로 읽어
Future<List<String>>을 돌려주는 메서드다. ↩ -
openRead()(스트림으로 읽기): 파일을 바이트 스트림(
Stream<List<int>>)으로 여는 메서드다. ↩ -
Uri(유알아이): URL을 표현하고 조합하는 클래스다. ↩
-
http.read()(본문만 읽기): 요청이 성공하면 본문 문자열을 돌려주고, 실패하면 예외를 던지는 함수다. ↩
-
statusCode(상태 코드): HTTP 응답의 성공/실패 등을 나타내는 숫자 코드다. ↩
-
body(본문): HTTP 응답의 본문 문자열이다. ↩
-
File(파일): 파일 경로를 기준으로 읽기/쓰기 작업을 제공하는 클래스다. ↩
-
readAsBytes()(바이트로 읽기): 파일 전체를 바이트 리스트로 읽어
Future<List<int>>를 돌려주는 메서드다. ↩ -
utf8.decoder(UTF-8 디코더): 바이트 스트림을 문자열 스트림으로 바꾸는 변환기다. ↩
-
LineSplitter(라인 스플리터): 문자열 스트림을 줄 단위로 나누는 변환기다. ↩
-
FileMode.write(쓰기 모드): 기존 파일 내용을 덮어쓰는 파일 쓰기 모드다. ↩
-
FileMode.append(추가 모드): 파일 끝에 이어서 쓰는 파일 쓰기 모드다. ↩
-
flush()(플러시): 버퍼에 남은 데이터를 즉시 내보내는 메서드다. ↩
-
close()(닫기): sink를 닫고 자원을 정리하는 메서드다. ↩
-
Client(클라이언트): 여러 요청을 보낼 때 재사용할 수 있는 HTTP 클라이언트다. ↩
-
close()(클라이언트 닫기):
Client를 닫아 자원을 정리하는 메서드다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!