본문 바로가기

모바일 프로그래밍(Android Studio)

YouTube Data API를 이용하여 검색 앱 만들기

더보기
  1. response JSON 의 구조를 파악하여, 원하는 화면으로 구성 하시오.
  2. 에디티텍스트가 하나 있어서, 유저한테 검색어를 입력받습니다.
  3. 검색 결과는 20개씩 받아오도록 요청하여야 합니다.
  4. 리사이클러뷰를 꼭 적용하여, 리스트로 보여줘야 합니다.
  5. 썸네일 이미지를 클릭하면, 새로운 액티비티에 큰 이미지로 보여줘야 합니다.
  6.  리사이클러뷰의 카드뷰를 클릭하면, 웹을 실행시켜서 비디오를 보여줍니다.
    응답 에서 "videoId" 의 값을 아래 url에 붙여서, 웹 뷰를 띄우시오.
     https://www.youtube.com/watch?v= "videoId"
    (예 https://www.youtube.com/watch?v=tnUslFhxRTs )
  • MainActivity.java
  • YoutubeAdapter.java
  • ImageViewerActivity.java
  • Youtube.java
  • Config.java

 


▶MainActivity.java

package com.example.yutubeapp3;

import static com.example.yutubeapp3.Config.YOUTUBE_API_KEY;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.yutubeapp3.adapter.YoutubeAdapter;
import com.example.yutubeapp3.model.Youtube;
import com.google.android.material.snackbar.Snackbar;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

        RecyclerView rv;
        YoutubeAdapter adapter;
        ArrayList<Youtube> youtubeList;

        ProgressBar pb;


        ImageView imgSearch;
        EditText editSearch;

        String URL;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);


            editSearch = findViewById(R.id.editSearch);
            imgSearch = findViewById(R.id.imgSearch);
            pb = findViewById(R.id.pb);

            rv = findViewById(R.id.rv);
            rv.setHasFixedSize(true);
            rv.setLayoutManager(new LinearLayoutManager(MainActivity.this));

            // 검색 이미지 클릭시 검색어 검색
            imgSearch.setOnClickListener(view -> {
                String searchURL = "https://www.googleapis.com/youtube/v3/search?part=snippet" + YOUTUBE_API_KEY;
                String keyword = "&q=" + editSearch.getText().toString().trim();
                String maxResultsParam = "&maxResults=20";
                URL = searchURL + keyword + maxResultsParam;

                if(editSearch.getText().toString().trim().isEmpty()){
                    Snackbar.make(imgSearch,"검색어를 입력하세요",Snackbar.LENGTH_SHORT).show();
                    return;
                }else{
                    // Json 네트워크 통신 메소드
                    getData();
                }
                // 강사님은 youtubeList.clear()사용함 왜냐면 검색할때마다 관련내용이 계속 어레이리스트에 추가되기 때문이다
            });

        }

        public void getData() {

            RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
            // 네트워크 통신을 위한 JsonObjectRequest 객체 생성
            // 생성자 : http Method, API URL, 전달 할 데이터, 실행 코드(Listener)
            JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, URL, null,
                    response -> {
                        // 강사는 URL부분에 Config.HOST + Config.PATH + "?" + Config.YOUTUBE_API_KEY + "" 를 썼다
                        // API 호출 결과 실행
                        try {

                            youtubeList = new ArrayList<Youtube>();

                            JSONArray jarr = response.getJSONArray("items");

                            for (int i = 0; i < jarr.length(); i++) {
                                JSONObject jdata = jarr.optJSONObject(i);

                                // 유튜브 비디오 ID
                                JSONObject jid = jdata.optJSONObject("id");
                                String videoId = jid.optString("videoId");

                                // 유튜브 제목과 내용
                                JSONObject jsnippet = jdata.optJSONObject("snippet");
                                String title = jsnippet.optString("title");
                                String description = jsnippet.optString("description");

                                // 유튜브 썸네일 이미지 디폴트
                                JSONObject jthumb = jsnippet.optJSONObject("thumbnails");
                                JSONObject jmedium = jthumb.optJSONObject("medium");
                                String thumbUrl = jmedium.optString("url");

                                // 유튜브 썸네일 이미지 가장 큰 크기
                                JSONObject jhigh = jthumb.optJSONObject("high");
                                String thumbUrlHigh = jhigh.optString("url");

                                Youtube youtube = new Youtube(videoId, title, description, thumbUrl, thumbUrlHigh);
                                youtubeList.add(youtube);
                            }
                            adapter = new YoutubeAdapter(MainActivity.this, youtubeList);
                            rv.setAdapter(adapter);
                            adapter.notifyDataSetChanged();


                        } catch (JSONException e) {
                            Snackbar.make(imgSearch,"데이터 파싱 에러",Snackbar.LENGTH_SHORT).show();
                            return;

                        }
                    }, error -> {
                Log.i("onErrorResponse", "" + error);
            });
            jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy(60000,
                    DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

            requestQueue.add((jsonObjectRequest));
        }
    }

▶YoutubeAdapter.java

package com.example.yutubeapp3.adapter;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
import com.example.yutubeapp3.ImageViewerActivity;
import com.example.yutubeapp3.R;
import com.example.yutubeapp3.model.Youtube;

import java.util.ArrayList;

public class YoutubeAdapter extends RecyclerView.Adapter<YoutubeAdapter.ViewHolder>{
    Context context;
    ArrayList<Youtube> youtubeList;
    Youtube youtube;

    public YoutubeAdapter(Context context, ArrayList<Youtube> youtubeList) {
        this.context = context;
        this.youtubeList = youtubeList;
    }

    @NonNull
    @Override
    public YoutubeAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.youtube_row, parent, false);
        return new YoutubeAdapter.ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        youtube = youtubeList.get(position);
        holder.txtTitle.setText(youtube.title);
        holder.txtDesc.setText(youtube.description);

        GlideUrl url = new GlideUrl(youtube.thumbUrl, new LazyHeaders.Builder().addHeader("User-Agent", "Android").build()); // 강사님은 이 부분 안씀
        Glide.with(context).load(url).into(holder.imgThumb);
    }

    @Override
    public int getItemCount() {
        return youtubeList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView txtTitle, txtDesc, txtAlbumId;
        ImageView imgThumb;
        CardView cardView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            txtTitle = itemView.findViewById(R.id.txtTitle);
            txtDesc = itemView.findViewById(R.id.txtBody);
            imgThumb = itemView.findViewById(R.id.imgView);
            cardView = itemView.findViewById(R.id.cardView);

            imgThumb.setOnClickListener(view -> {
                int index = getAdapterPosition();
                youtube = youtubeList.get(index);
                Intent intent = new Intent(context, ImageViewerActivity.class);
                intent.putExtra("url", youtube.thumbUrlHigh);
                context.startActivity(intent);
            });

            cardView.setOnClickListener(view -> {
                int index = getAdapterPosition();
                youtube = youtubeList.get(index);

                String webUrl = "https://m.youtube.com/watch?v=" + youtube.videoId;

                // IopenWebPage();

                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webUrl));
                context.startActivity(intent);
            });
        }


        // 강사님 코드
        //        void openWebPage(String url){
//            // 여행숙소앱이다 숙소 홈페이지로 들어갈 수 있게 한다
//            Uri uri = Uri.parse(url);
//            Intent intent = new Intent(Intent.ACTION_VIEW,uri);
//            context.startActivity(intent);
//        }
    }
}

▶ImageViewerActivity.java

package com.example.yutubeapp3;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;

public class ImageViewerActivity extends AppCompatActivity {
    ImageView img;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_viewer);

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        img = findViewById(R.id.imgBig);

        String imgUrl = getIntent().getStringExtra("url");

        GlideUrl url = new GlideUrl(imgUrl, new LazyHeaders.Builder().addHeader("User-Agent", "Android").build());
        Glide.with(ImageViewerActivity.this).load(url).into(img);
    }

    @Override
    public boolean onSupportNavigateUp() {
        finish();
        return true;
    }
}

▶Youtube.java

package com.example.yutubeapp3.model;

public class Youtube {
    public String videoId, title, description, thumbUrl, thumbUrlHigh;

    public Youtube(String videoId, String title, String description, String thumbUrl, String thumbUrlHigh) {
        this.videoId = videoId;
        this.title = title;
        this.description = description;
        this.thumbUrl = thumbUrl;
        this.thumbUrlHigh = thumbUrlHigh;
    }
}

▶ Config.java

package com.example.yutubeapp3;

public class Config {
    // 강사코드
    //public static final String HOST = "https://www.googleapis.com";
    //public static final String PATH = "/youtube/v3/search";
    public static final String YOUTUBE_API_KEY = "&key=구글 API 키";
}

▶ API응답과 코드부분 비교

더보기

{

  "kind": "youtube#videoListResponse",
  "etag": "\"UCBpFjp2h75_b92t44sqraUcyu0/sDAlsG9NGKfr6v5AlPZKSEZdtqA\"",
  "videos": [
    {
      "id": "7lCDEYXw3mM",
      "kind": "youtube#video",
      "etag": "\"UCBpFjp2h75_b92t44sqraUcyu0/iYynQR8AtacsFUwWmrVaw4Smb_Q\"",
      "snippet": {
        "publishedAt": "2012-06-20T22:45:24.000Z",
        "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
        "title": "Google I/O 101: Q&A On Using Google APIs",
        "description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg"
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg"
          },
          "high": {
            "url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg"
          }
        },
        "categoryId": "28"
      },
      "contentDetails": {
        "duration": "PT15M51S",
        "aspectRatio": "RATIO_16_9"
      },
      "statistics": {
        "viewCount": "3057",
        "likeCount": "25",
        "dislikeCount": "0",
        "favoriteCount": "17",
        "commentCount": "12"
      },
      "status": {
        "uploadStatus": "STATUS_PROCESSED",
        "privacyStatus": "PRIVACY_PUBLIC"
      }
    }
  ]
}

더보기

        imgSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String keyword = editSearch.getText().toString().trim();

                if(keyword.isEmpty()){
                    Snackbar.make(imgSearch,
                            "검색어를 입력하세요",
                            Snackbar.LENGTH_SHORT).show();
                    return;
                }

                videoArrayList.clear();

                progressBar.setVisibility(View.VISIBLE);

                RequestQueue queue = Volley.newRequestQueue(MainActivity.this);

                JsonObjectRequest request = new JsonObjectRequest(
                        Request.Method.GET,
                        Config.HOST + Config.PATH + "?key=" + Config.GCP_API_KEY + "&&part=snippet&maxResults=20&type=video&q=" + keyword,
                        null,
                        new Response.Listener<JSONObject>() {
                            @Override
                            public void onResponse(JSONObject response) {

                                progressBar.setVisibility(View.GONE);

                                try {
                                    JSONArray items = response.getJSONArray("items");

                                    for(int i = 0; i < items.length(); i++){
                                      JSONObject object = items.getJSONObject(i);

                                      JSONObject id = object.getJSONObject("id");
                                      String videoId = id.getString("videoId");

                                      JSONObject snippet = object.getJSONObject("snippet");
                                      String title  = snippet.getString("title");
                                      String description = snippet.getString("description");

                                      JSONObject thumbnails = snippet.getJSONObject("thumbnails");
                                      JSONObject medium = thumbnails.getJSONObject("medium");
                                      String url = medium.getString("url");

                                      Video video = new Video(title, description, url, videoId);

                                      videoArrayList.add(video);

                                    }

                                } catch (JSONException e) {

                                    Snackbar.make(imgSearch,
                                            "데이터 파싱 에러.",
                                            Snackbar.LENGTH_SHORT).show();
                                    return;
                                }

                                adapter = new VideoAdapter(MainActivity.this, videoArrayList);
                                recyclerView.setAdapter(adapter);

                            }
                        },
                        new Response.ErrorListener() {
                            @Override
                            public void onErrorResponse(VolleyError error) {
                                progressBar.setVisibility(View.GONE);
                            }
                        }
                );

                queue.add(request);
            }
        });

snippet에 title과 description이있고, thumbnails에 medium 그리고 medium에 url이 있기때문에 snippet, thumbnails를 먼저 가져온다. 그 다음은 title, description가져오고, medium 가져오고나서 url 가져오면된다.