목차

  1. 사용된 기능 설명
  2. 데이터 전달 로직
  3. 사용 예시

 

 

✅ 사용된 기능 설명

  • 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) 데이터를 가져오는 속성

 

 

✅ 데이터 전달 로직

 

  1. View (album_view.dart)
    • 사용자가 페이지를 열면 Bloc에 데이터 요청 (bloc.fetchAllAlbums())
  2. Bloc (album_bloc.dart)
    • Repository에 데이터 요청
    • 받은 데이터 sink.add() → Stream으로 UI에 전달
  3. Repository (album_repository.dart)
    • Api_Provider 호출해서 서버 데이터 가져옴
    • JSON → Model 변환
  4. Data Provider (api_provider.dart)
    • 서버에 실제 API 요청 (http.get)
    • JSON 데이터 반환
  5. Model (album.dart, albums.dart)
    • JSON → Album 객체 변환
    • 앱 내부에서 사용 가능하게 정리
  6. View (album_view.dart)
    • StreamBuilder가 데이터 구독
    • 데이터가 들어오면 화면 자동 업데이트

 

 

✅ 사용 예시

  1. https 로 받을 더미 데이터 
  2. pubspec.yaml 설정
  3. model
  4. data_provider
  5. repository
  6. bloc
  7. view

 

1️⃣ https 로 받을 더미 데이터 

  • https://jsonplaceholder.typicode.com/albums

https://jsonplaceholder.typicode.com/albums

 

 

2️⃣ pubspec.yaml 설정

  • pubspec.yaml 설정
  • 터미널 명령어 입력

pubspec.yaml 설정
터미널 명령어 입력

 

 

3️⃣ model

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

album.dart
albums.dart

 

 

4️⃣ data_provider

  • api_provider.dart : http 서버에서 받은 JSON 데이터를 받을 때 성공 여부를 처리

api_provider.dart

 

 

5️⃣ repository

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

album_repository.dart

 

 

6️⃣ bloc

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

album_bloc.dart

 

 

7️⃣ view

  • album_view.dart : 초기화 메소드 및 Scaffold 위젯 작성

album_view.dart

 

 

8️⃣ main.dart

  • home : 앱 실행시 실행할 메소드 수정

main.dart

 

 

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,
                ),
              );
            }
          },
        ),
      ),
    );
  }
}

+ Recent posts