15장 코팩튜브

송기영·2024년 1월 2일
0

플러터

목록 보기
17/25

15.1 사전지식

15.1.1 HTTP 기반 API 종류

종류설명
REST APIHTTP의 GET, POST, PUT, DELETE을 사용하는 가장 대중적인 API
GraphQLGraph 구조를 띄우며 클라이언트에서 직접 필요한 데이터를 명시할 수 있는 형태의 통신으로 필요한 데이터만 가져올 수 있다는 장점이 있다.
gRPCHTTP/2를 사용하는 통신방식으로 Protocol Buffers라는 방식을 사용하며 레이턴시를 최소화할 목적으로 설계되었다.
  • GET : 서버로부터 데이터를 가져온다.
  • POST : 데이터를 서버에 저장한다.
  • PUT : 데이터를 업데이트한다.
  • DELETE : 데이터를 삭제한다.

💡TIP: PUT과 PATCH의 차이점 PUT은 리소스의 모든 것을 업데이트할때 사용하고 PATCH는 리소스의 일부를 업데이트 할 때 사용한다.

15.1.2 Dio 플러그인

플러터에서 일반적으로 http플러그인이나 dio플러그인을 사용한다.

import "package:dio/dio.dart";

void main() async {
	// get 요청
	final getResp = Dio().get("http://test");

	// post 요청
	final postResp = Dio().post("http://test");

	// put 요청
	final putResp = Dio().put("http://test");

	// delete 요청
	final deleteResp = Dio().delete("http://test");
}

15.2 사전준비

15.2.1 유튜브 API 설정하기

구글 클라우드 플랫폼에서 발급한 토큰이 필요하다. 13장에서 발행한 토큰을 활용하면 되지만 추가 설정이 필요하다.

  • 구글 클라우드 콘솔에 접속해서 YoutubeData API V3를 검색 후 사용한다.

15.2.2 pubspec.yaml 설정하기

dependencies:
  cupertino_icons: ^1.0.6
  dio: ^5.4.0
  flutter:
    sdk: flutter
  youtube_player_flutter: ^8.1.2

15.2.3 네이티브 설정하기

  • 안드로이드

15.3 구현하기

15.3.1 VideoModel Class

// lib/model/video_model.dart

class VideoModel {
  // 동영상 ID
  final String id;
  // 동영상 제목
  final String title;

  VideoModel({required this.id, required this.title});
}

15.3.2 HomeScreen

// lib/screen/home_screen.dart

import 'package:cf_tube/component/custom_youtube_player.dart';
import 'package:cf_tube/model/video_model.dart';
import 'package:cf_tube/repository/youtube_repository.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});
  
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        centerTitle: true,
        title: const Text("코팩튜브"),
        backgroundColor: Colors.black,
      ),
      body: FutureBuilder<List<VideoModel>>(
        future: YoutubeRepository.getVideos(),
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return Center(
              child: Text(snapshot.error.toString()),
            );
          }

          if (!snapshot.hasData) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }

          return RefreshIndicator(
              child: ListView(
                // 아래로 당겨서 스크롤할 때 튕기는 애니메이션 추가
                physics: const BouncingScrollPhysics(),
                children: snapshot.data!
                    .map((e) => CustomYoutubePlayer(videoModel: e))
                    .toList(),
              ),
              onRefresh: () async {
                setState(() {});
              });
        },
      ),
    );
  }
}

15.3.3 YoutubeRepository

// lib/repository/youtube_repository.dart

import "package:cf_tube/const/api.dart";
import "package:dio/dio.dart";
import "package:cf_tube/model/video_model.dart";

class YoutubeRepository {
  static Future<List<VideoModel>> getVideos() async {
    final resp = await Dio().get(YOUTUBE_API_BASE_URL, queryParameters: {
      "channelId": CF_CHANNEL_ID,
      "maxResults": 50,
      "key": API_KEY,
      "part": "snippet",
      "order": "date",
    });

    // videoId와 title이 null이 아닌 값들만 필터링
    final listWithData = resp.data["items"].where(
      (item) =>
          item?["id"]?["videoId"] != null && item?["snippet"]?["title"] != null,
    );

    return listWithData
        .map<VideoModel>((item) => VideoModel(
            id: item["id"]["videoId"], title: item["snippet"]["title"]))
        .toList();
  }
}

15.3.4 VideoModel

// lib/model/video_model.dart

class VideoModel {
  // 동영상 ID
  final String id;
  // 동영상 제목
  final String title;

  VideoModel({required this.id, required this.title});
}

15.3.5 CustomYoutubePlayer

// lib/component/custom_youtube_player.dart

import "package:flutter/material.dart";
import "package:cf_tube/model/video_model.dart";
import "package:youtube_player_flutter/youtube_player_flutter.dart";

class CustomYoutubePlayer extends StatefulWidget {
  // 상위 위젯에서 입력받을 동영상 정보
  final VideoModel videoModel;
  const CustomYoutubePlayer({required this.videoModel});
  
  State<CustomYoutubePlayer> createState() => _CustomYoutubePlayerState();
}

class _CustomYoutubePlayerState extends State<CustomYoutubePlayer> {
  YoutubePlayerController? controller;

  
  void initState() {
    super.initState();

    controller = YoutubePlayerController(
        initialVideoId: widget.videoModel.id,
        flags: const YoutubePlayerFlags(
          autoPlay: false,
        ));
  }

  
  void dispose() {
    super.dispose();
    controller!.dispose();
  }

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 유튜브 재생기 렌더링
        YoutubePlayer(
          controller: controller!,
          showVideoProgressIndicator: true,
        ),
        const SizedBox(
          height: 16.0,
        ),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Text(
            widget.videoModel.title,
            style: const TextStyle(
                color: Colors.white,
                fontSize: 16.0,
                fontWeight: FontWeight.w700),
          ),
        ),
        const SizedBox(
          height: 16.0,
        )
      ],
    );
  }
}

15.3.6 const

// lib/const/api.dart

const API_KEY = "구글 클라우드 플랫폼 토큰";
const YOUTUBE_API_BASE_URL = "https://youtube.googleapis.com/youtube/v3/search";
const CF_CHANNEL_ID = "UCAJ-meoCh1TrPZ7La3UpPrw";
profile
업무하면서 쌓인 노하우를 정리하는 블로그🚀 풀스택 개발자를 지향하고 있습니다👻

0개의 댓글