외로운 Nova의 작업실

Flutter 프로그래밍 - 10(코팩 튜브) 본문

Programming/Flutter

Flutter 프로그래밍 - 10(코팩 튜브)

Nova_ 2024. 1. 20. 13:59

안녕하세요, 이번 장에서는 코드팩토리 유튜브를 api로 가져오고 ListView로 뿌려주는 앱을 만들어보겠습니다.

 

 

- 사전 지식

플러터에서는 http 플러그인이나 dio 플러그인을 사용합니다. 

 

- 사전 준비

프로젝트 이름 : cf_tube

 

이제 유튜브 API를 설정해야합니다. 이는 구글 클라우드 플랫폼에서 발급한 토큰이 필요합니다. 먼저 구글 클라우드 플랫폼에 가입하고 프로젝트를 생성해줍니다.

그리고 검색을 통해 유튜브 api를 사용해줍니다.

사용자 인증 정보를 만들어서 API키를 발급해줍니다.

API키값을 잘 저장해줍니다.

 

- pubspec.yaml 파일 설정

flutter 패키지들을 설정해줍니다.

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  youtube_player_flutter: 8.1.0 #플레이어 추가
  dio: 4.0.6 #dio 추가

 

- 네이티브 설정

이번 프로젝트를 실행하려면 android/app/build.gradle 파일의 compileSdkBersion을 33으로 설정하고 minSdkVersion을 17로 설정해줍니다.

plugins {
    id "com.android.application"
    id "kotlin-android"
    id "dev.flutter.flutter-gradle-plugin"
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

android {
    namespace "com.example.cf_tube"
    compileSdkVersion 33 //버전 33으로 변경
    ndkVersion flutter.ndkVersion

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.cf_tube"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        minSdkVersion 17 //17로 변경
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {}

 

- 프로젝트 초기화

이제 lib 폴더에 screen 폴더를 생성하고 Home_Screen.dart를 생성하겠습니다.

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget{
  
  const HomeScreen({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context){
    return Scaffold(
      body: Text("Home Screen"),
    );
  }
}

 

아래는 main.dart 파일입니다.

import 'package:flutter/material.dart';
import 'package:cf_tube/screen/home_screen.dart';

void main() {
  runApp(MaterialApp(
    home: HomeScreen(),
  ));
}

 

- 레이아웃 구상

 

 

- 구현하기

Http 요청의 응답을 받을 모델 클래스를 구현하고 이 모델 클래스를 가지고 CustomYoutubePlayer를 만들어 UI에 여러개 뿌려줄 겁니다. UI를 작성한다음 Dio를 사용해서 직접 API 요청을 진행하겠습니다. 이후 리스트를 갱신할 수 있는 새로고침 기능을 만들겠습니다.

 

- VideoModel 구현하기

유튜브 API를 사용하면 굉장히 많은 정보를 가져올 수 있습니다. 그중에서 동영상 ID와 제목만 활용하겠습니다. 이정보를 담을 VideoModel 클래스를 구현해서 데이터를 관리하겠습니다. /lib/model/video_model.dart 파일을 생성한후 VideoModel 클래스를 생성하겠습니다.

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

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

 

- CustomYoutubePlayer 위젯

CustomYoutubePlayer 위젯은 유튜브 동영상을 재생할 수 있는 상태로 제공하는 역할을 합니다. 이미 추가한 플러그인인 youtube_player_flutter 플러그인을 사용해서 구현하겠습니다. 먼저 lib/component/custom_youtube_player.dart 파일을 생성하고 videoModel 변수를 입력받고 이정보를 바탕으로 유튜브 UI를 구현하는 위젯을 만들어보겠습니다.

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,
    Key? key
}) : super(key: key);


  @override
  State<CustomYoutubePlayer> createState() => _CustomYoutubePlayer();
  
}

class _CustomYoutubePlayer extends State<CustomYoutubePlayer>{
  
  @override
  Widget build(BuildContext context){
    
    return Container(); //임시로 컨테이너 반환
  }
}

YoutubePlayer 위젯을 조정하려면 YoutubePlayerController 위젯을 사용해야합니다. 지금까지 모든 컨트롤러에 대해 해왔듯이 YoutubePlayerController를 initState()함수에서 초기화해주고 dispose()함수에서 폐기해주겠습니다. 추가적으로 YouTubePlayer위젯고 Text 위젯을 세로로 배치해서 동영상 위에 동영상 제목이 있는 형태의 UI로 커스텀플레이어로 만들겠습니다.

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,
    Key? key
}) : super(key: key);


  @override
  State<CustomYoutubePlayer> createState() => _CustomYoutubePlayer();

}

class _CustomYoutubePlayer extends State<CustomYoutubePlayer>{

  YoutubePlayerController? controller;

  @override
  void initState(){
    super.initState();

    controller = YoutubePlayerController(
      initialVideoId: widget.videoModel.id, //처음 실행할 동영상의 ID
      flags: YoutubePlayerFlags(
        autoPlay: false, //자동 실행 금지
      )
    );
  }

  @override
  Widget build(BuildContext context){

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Padding(padding: const EdgeInsets.symmetric(horizontal: 8.0),
        child: Text(
          widget.videoModel.title,
          style: TextStyle(
            color: Colors.white,
            fontSize: 16.0,
            fontWeight: FontWeight.w700,
          ),
        ),),

        const SizedBox(height: 16.0,),

        YoutubePlayer(controller: controller!,
        showVideoProgressIndicator: true,),

        const SizedBox(height: 16.0,),
      ],
    );
  }

  @override
  void  dispose(){

    controller!.dispose(); //State 폐기시 컨트롤러 또한 폐기
  }
}

 

이제 home_screen에 예시로 모델을 전달하고 하나의 커스텀플레이어를 출력해보겠습니다.

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

class HomeScreen extends StatelessWidget{

  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context){
    return Scaffold(
      backgroundColor: Colors.black,
      body: CustomYoutubePlayer(
        videoModel: VideoModel(
          id: '3Ck42C2ZCb8',
          title: '다트 언어 기본기 1시간만에 끝내기'
        ),
      ),
    );
  }
}

잘 나오는것을 확인할 수 있습니다.

 

- YoutubeRepository 구현하기

현재 HomeScreen에는 직접 제공해준 영상 ID를 기반으로 화면을 렌더링해주고있습니다. 지정한 동영상들만 보여주면 재미가 없으므로 googleyoutubeapi를 통해 코드팩토리 채널 ID를 활용해 최신 영상리스트정보들을 받아오고 이를 YoutubeRepository 클래스에 저장한다음 getVideos() 메서드 사용시 최신 영상리스트 정보에서 필요한 정보만 뽑아서 VideoModel를 여러개 만든다음 VideoModel들을 List로 만들어서 반환해주는 클래스를 만들어보겠습니다.

우선 구글 API관련 정보를 저장할 파일을 만들어주겠습니다. lib/const/api.dart파일을 만들어줍니다.

const API_KEY = 'AIzaSyDgZJ2vajvDk7bnbo0UZU_nCHbZBa8v71w';

//Youtube Data Api V3 UrL
const YOUTUBE_API_BASE_URL = 'https://youtube.googleapis.com/youtube/v3/search';
const CF_CHNNEL_ID = 'UCxZ2AlaT0hOmxzZVbF_j_Sw';

채널 ID는 채널 페이지에 들어갔을때 URL의 마지막 부분에서 가져올 수 있습니다.

 

이제 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 dio = Dio(); // Dio 클래스의 인스턴스 생성

    final resp = await dio.get(
      YOUTUBE_API_BASE_URL,
      queryParameters: {
        'channelId': CF_CHANNEL_ID, // 'ChnnelId' 대신 'channelId'로 수정
        'maxResults': 50,
        'key': API_KEY,
        'part': 'snippet',
        'order': 'date',
      },
    );

    if (resp.data != null) { // 응답 데이터에 대한 null 체크
      final listWithData = resp.data['items'].where(
            (item) =>
        item?['id']?['videoId'] != null && item?['snippet']?['title'] != null,
      ); // null이 아닌 값만 필터링

      return listWithData.map<VideoModel>(
            (item) => VideoModel(
          id: item['id']['videoId'],
          title: item['snippet']['title'],
        ),
      ).toList();
    } else {
      return []; // 응답 데이터가 없는 경우 빈 리스트 반환
    }
  }
}

 

- ListView 구현

이제 home_screen에 youtube_repository가 반환하는 ListView들을 보여줄 UI 로직을 짜보겠습니다. 이때 사용할 위젯은 FutureBuilder 위젯으로 이 위젯은 비동기적으로 데이터를 가져오고 그 데이터를 이용해서 Builder를 통해 ListView를 반환할 수 있습니다.

import 'package:flutter/material.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';

class HomeScreen extends StatelessWidget{

  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context){
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        centerTitle: true, //제목 가운데 정렬
        title: 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 Center(
            child: CircularProgressIndicator(),
          );
        }
        
        return ListView(
          physics: BouncingScrollPhysics(), //스크롤할때 튕기는 애니메이션 추가
          children: snapshot.data!
          .map((e) => CustomYoutubePlayer(videoModel: e))
          .toList(),
        );
      },
    ),
      
    );
  }
}

 

map함수는 snapshot.data(리스트)에서 각각 리스트 요소를 e 객체에 담아서 CustomYoutubePlayer 위젯을 만들고 이를 toList함수로 List로 만들어 ListView가 사용할 수 있도록 합니다.

한번 실행해보겠습니다.

 

잘나오는 것을 볼 수 있습니다.

 

- 새로고침 기능 구현

build 함수에는 FutureBuilder를 사용하면 FutureBuilder가 화면에 렌더링 될때마다 future 매개변수에 입력된 함수가 실행됩니다. 현재 우리 앱은 앱을 처음 시작했을때만 동영상 데이터를 가져오고 있습니다. 하지만 실전 앱이라면 새로운 사용자가 원힐때 새로운 데이터를 가져올 수 있는 기능을 추가해줘야합니다. 앱에서는 보통 리스트를 아래로 당기는 동작에 추가합니다. 이 기능을 추가하겠습니다.

import 'package:flutter/material.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';

class HomeScreen extends StatefulWidget{
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>{

  @override
  Widget build(BuildContext context){
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        centerTitle: true, //제목 가운데 정렬
        title: 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 Center(
            child: CircularProgressIndicator(),
          );
        }

        return RefreshIndicator(onRefresh: () async {
          setState(() {

          });
        },
          child: ListView(
            physics: BouncingScrollPhysics(), //스크롤할때 튕기는 애니메이션 추가
            children: snapshot.data!
                .map((e) => CustomYoutubePlayer(videoModel: e))
                .toList(),
          ),
        );
      }),
    );
  }
}

 

실행해보겠습니다.

 

위에서 아래로 당기면 새로고침 표시도 보입니다.

Comments