목차
- 사용된 기능 설명
- 데이터 전달 로직
- 사용 예시
✅ 사용된 기능 설명
- factory : 생성자로써, 새 객체를 반드시 만들 필요가 없음
** JSON 파싱(서버 응답을 객체로 바꿀 때) 주로 사용됨
** 하나의 클래스에서 하나의 인스턴스만 생성 (싱글톤 패턴) - dynamic : 어떤 타입이든 될 수 있다는 뜻
- required : 해당 파라미터에 '무조건' 값이 전달돼야 할때 사용
- in : 플러터의 for문에서 사용되며, java로 표현한다면 ":" 역할
** ex. for (dynamic val in json) → for (Object val : json) - show Client : http 패키지 안에 있는 Client 클래스를 가져옴 (
** 다른 것들 (Response, get(), post() 등)은 가져오지 않음
** Dart의 import 문에는 show / hide 키워드를 붙여서, 가져올 심볼(클래스, 함수, 변수) 을 제한할 수 있음 - .get() : 서버에 GET 요청
- http.get(...) : 서버에 GET 요청을 보냄
** package:http의 get - Uri.parse() : URL 문자열을 Uri 객체로 변환
** HTTP 요청에 적합한 형태 - .body : HTTP 응답의 본문(body) 데이터를 가져오는 속성
✅ 데이터 전달 로직
- View (album_view.dart)
- 사용자가 페이지를 열면 Bloc에 데이터 요청 (bloc.fetchAllAlbums())
- Bloc (album_bloc.dart)
- Repository에 데이터 요청
- 받은 데이터 sink.add() → Stream으로 UI에 전달
- Repository (album_repository.dart)
- Api_Provider 호출해서 서버 데이터 가져옴
- JSON → Model 변환
- Data Provider (api_provider.dart)
- 서버에 실제 API 요청 (http.get)
- JSON 데이터 반환
- Model (album.dart, albums.dart)
- JSON → Album 객체 변환
- 앱 내부에서 사용 가능하게 정리
- View (album_view.dart)
- StreamBuilder가 데이터 구독
- 데이터가 들어오면 화면 자동 업데이트

✅ 사용 예시
- https 로 받을 더미 데이터
- pubspec.yaml 설정
- model
- data_provider
- repository
- bloc
- view
1️⃣ https 로 받을 더미 데이터
- https://jsonplaceholder.typicode.com/albums

2️⃣ pubspec.yaml 설정
- pubspec.yaml 설정
- 터미널 명령어 입력


3️⃣ model
- album.dart : 생성자 & 인스턴스 세팅
- albums.dart : 서버에서 JSON 타입 데이터를 album.dart에서 세팅한 타입의 List로 받기


4️⃣ data_provider
- api_provider.dart : http 서버에서 받은 JSON 데이터를 받을 때 성공 여부를 처리

5️⃣ repository
- album_repository.dart : 4️⃣에서 받은 데이터를 가공 및 저장

6️⃣ bloc
- album_bloc.dart : 5️⃣에서 가공 및 저장한 데이터를 stream에 전달

7️⃣ view
- album_view.dart : 초기화 메소드 및 Scaffold 위젯 작성


8️⃣ main.dart
- home : 앱 실행시 실행할 메소드 수정

9️⃣ 테스트 화면

✔️ 사용된 코드
1️⃣ model
class Album{
int? userId; // 타입에 물음표(?)가 붙으면 null 허용(nullable)
int? id;
String? title;
// 생성자 : 객체가 만들어질 때 값을 넣어주는 함수
Album({
this.userId,
this.id,
this.title
});
// JSON → Album 변환
// 서버에서 받은 JSON → Dart 객체 변환 역할하는 메소드
// factory : 생성자로써, 새 객체를 반드시 만들 필요가 없음,
// - JSON 파싱(서버 응답을 객체로 바꿀 때) 주로 사용됨
// dynamic : 어떤 타입이든 될 수 있다는 뜻
factory Album.fromJSON(Map<String, dynamic> json) =>
Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
import './album.dart';
class Albums {
late List<Album> albums; // 데이터를 나중에 받을, Album 객체의 List 타입을 담을 albums 변수 선언
// required : 해당 파라미터에 '무조건' 값이 전달돼야 할때 사용
Albums({required this.albums});
// JSON 데이터를 받아 Albums 객체를 생성
Albums.fromJSON(List<dynamic> json){ // 파라미터로 들어오는 json은 리스트 형태 (List<dynamic>)
// 빈 리스트(.empty)를 만들지만 리스트 크기는 늘어날 수 있음
albums = List<Album>.empty(growable: true); // 'growable: true' : 리스트의 크기가 늘어날 수 있다는 뜻이며, add 사용 가능
for (dynamic val in json){ // java로 표현한다면 "for (Object val : json)"
albums.add(Album.fromJSON(val));
}
}
}
2️⃣ data_provider
import 'dart:convert';
import 'package:프로젝트명/model/albums.dart';
import 'package:http/http.dart' show Client;
// 1. import '패키지 경로'; : 외부 패키지나 라이브러리를 가져오는 Dart 문법
// 2. 'package:http/http.dart' : http 패키지 안에 있는 http.dart 파일을 가져옴
// 3. show Client : http 패키지 안에 있는 Client 클래스를 가져옴 (다른 것들 (Response, get(), post() 등)은 가져오지 않음)
// - Dart의 import 문에는 show / hide 키워드를 붙여서, 가져올 심볼(클래스, 함수, 변수) 을 제한할 수 있음
class AlbumApiProvider{
Client client = Client();
Future<Albums> fetchAlbumList() async{ // 비동기적으로 Albums 객체를 반환
// .get() : 서버에 GET 요청
// Uri.parse() : URL 문자열을 Uri 객체로 변환
final response = await client.get(Uri.parse('https://jsonplaceholder.typicode.com/albums'));
if(response.statusCode == 200){ // 서버 요청 성공(200)
// jsonDecode : JSON 문자열을 Dart 객체로 변환해주는 함수(JSON을 Map/List 형태로 변경)
// .body : HTTP 응답의 본문(body) 데이터를 가져오는 속성
final data = jsonDecode(response.body);
return Albums.fromJSON(data);
}else{
throw Exception('Failed to load album list');
}
}
}
3️⃣ repository
import 'package:프로젝트명/data_provider/api_provider.dart';
import '../model/albums.dart';
// api_provider에서 가져온 데이터를 가공 및 저장하는 부분 (여기서는 가공하진 않음)
class AlbumRepository{
final AlbumApiProvider _albumRepository = AlbumApiProvider();
Future<Albums> fetchAllAlbums() async => _albumRepository.fetchAlbumList();
}
4️⃣ bloc
import 'package:rxdart/rxdart.dart';
import 'package:프로젝트명/repository/album_repository.dart';
import '../model/albums.dart';
class AlbumBloc{
final AlbumRepository _albumRepository = AlbumRepository();
final PublishSubject<Albums> _albumFetcher = PublishSubject<Albums>(); // 데이터를 UI에 흘려보냄, 구독(Listener)이 있을 때만 이벤트 전달
Stream<Albums> get allAlbums => _albumFetcher.stream; // UI가 이 스트림을 listen하면, 데이터가 들어올 때마다 자동으로 화면 갱신 가능
Future<void> fetchAllAlbums() async{
Albums albums = await _albumRepository.fetchAllAlbums(); // album_repository의 fetchAllAlbums 메소드를 가져올 수 있음
// 데이터 fetch → 스트림에 전달 → UI가 자동으로 받음
_albumFetcher.sink.add(albums); // sink : 받은 데이터를 stream에 전달
}
dispose(){ // Stream 종료 (stream : 데이터가 흘러가는 통로)
_albumFetcher.close();
}
}
5️⃣ view
import 'package:flutter/material.dart';
import 'package:프로젝트명/bloc/album_bloc.dart';
import '../model/albums.dart';
class AlbumView extends StatefulWidget {
const AlbumView({super.key});
@override
State<AlbumView> createState() => _AlbumViewState();
}
class _AlbumViewState extends State<AlbumView> {
final AlbumBloc _albumBloc = AlbumBloc();
@override
void initState(){
_albumBloc.fetchAllAlbums();
super.initState();
}
@override
Widget build(BuildContext context){
return Material(
child: Scaffold(
appBar: AppBar(
title: const Text("BLoC 패턴 예제"),
),
body: StreamBuilder<Albums>(
stream: _albumBloc.allAlbums,
builder: (context, snapshot){
if(snapshot.hasData){
Albums? albumList = snapshot.data;
return ListView.builder(
itemCount: albumList?.albums.length,
itemBuilder: (context, index){
return Container(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("ID : ${albumList?.albums[index].id.toString()}"),
Text("TITEL : ${albumList?.albums[index].title}"),
]
),
);
},
);
}else if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString()),
);
}else{
return const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
),
);
}
},
),
),
);
}
}
'IT 언어 > Flutter' 카테고리의 다른 글
| [Flutter] GetX (Reactive State, Get.put()) [맥북💻] (0) | 2025.09.27 |
|---|---|
| [Flutter] GetX (Simple State, GetBuilder, Get.find, update()) [맥북💻] (0) | 2025.09.27 |
| [Flutter] 아래로 스와이프하여 새로고침 (RefreshIndicator) [맥북💻] (0) | 2025.09.23 |
| [Flutter] 앱 하단 네비게이션 (bottomNavigationBar, TabBar, setState) [맥북 💻] (0) | 2025.09.21 |
| [Flutter] 데이터를 기기에 저장하기 (SharedPreferences) [맥북💻] (0) | 2025.09.20 |