Android

Volley ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Retrofit2 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์œ ํŠœ๋ธŒ app ๊ฐœ๋ฐœํ•˜๊ธฐ

567Rabbit 2024. 6. 13. 13:41

Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

- ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” HTTP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

 

- Retrofit์€ Square์—์„œ ๊ฐœ๋ฐœํ•œ HTTP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

 

- ์ฃผ๋กœ Android ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ RESTful ์›น ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.

 

- ์ฆ‰, REST API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋„์™€์ค€๋‹ค.

 

- JSON์„ Java ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ๊ทธ ๋ฐ˜๋Œ€๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์ž‘์—…์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

- Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” JSON์„ ์ž๋ฐ”์˜ ํด๋ž˜์Šค๋กœ ๋ฐ”๋กœ ๋‹ด์•„์ฃผ๋ฏ€๋กœ ํŒŒ์‹ฑ์„ ํ•  ํ•„์š”๊ฐ€ ์—†์–ด์„œ ๊ฐ„ํŽธํ•˜๋‹ค.

 

- Retrofit์„ ์‚ฌ์šฉํ•˜๋ฉด ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

Retrofit (1.x)

- Retrofit 1.x๋Š” HTTP ํด๋ผ์ด์–ธํŠธ๋กœ Apache HTTPClient๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค

- ๊ธฐ๋ณธ์ ์œผ๋กœ Call ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์†Œ๋“œ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์œผ๋กœ ์ง์ ‘์ ์ธ POJO ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค

- RxJava์™€์˜ ํ†ตํ•ฉ์€ ๋ณ„๋„์˜ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์•„๋‹Œ ๋ฐฉ์‹์œผ๋กœ ์ œ๊ณต๋œ๋‹ค

- Gson์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ†ตํ•ฉ๋˜์–ด ์žˆ์—ˆ์œผ๋ฉฐ, ์ถ”๊ฐ€์ ์ธ ์„ค์ • ์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

 

Retrofit2

- Retrofit 2.x๋Š” HTTP ํด๋ผ์ด์–ธํŠธ๋กœ OkHttp๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค ์ด๋Š” ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋ฐ ์‘๋‹ต์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋” ํšจ์œจ์ ์œผ๋กœ ํ•œ๋‹ค

 

 

- ๋ฐ˜ํ™˜ ํƒ€์ž… : Call<T> ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค ์ด๋Š” ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค€๋‹ค

Call<MyResponse> call = api.getMyData();

 

 

- ์–ด๋Œ‘ํ„ฐ : ๋‹ค์–‘ํ•œ ํ˜ธ์ถœ ์–ด๋Œ‘ํ„ฐ(Call Adapters)๋ฅผ ์ง€์›ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, RxJava๋‚˜ Kotlin Coroutines์™€ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณ„๋„์˜ ์–ด๋Œ‘ํ„ฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค

@GET("users/{user}") Observable<User> getUser(@Path("user") String user);

 

 

- Gson ํ†ตํ•ฉ : Gson์€ ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ช…์‹œ์ ์œผ๋กœ Retrofit ๊ฐ์ฒด์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค

Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build();

 

 

 

https://github.com/square/retrofit

 

GitHub - square/retrofit: A type-safe HTTP client for Android and the JVM

A type-safe HTTP client for Android and the JVM. Contribute to square/retrofit development by creating an account on GitHub.

github.com

 

 

 

ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •ํ•˜๊ธฐ

 

 

 

 

 

Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ํ•˜๊ธฐ

 

 

 

implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")

 

 

Sync๋ฅผ ๋ˆŒ๋Ÿฌ์ฃผ๋ฉด ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋œ๋‹ค.

 

 

 

 

 

Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฝ”๋“œ ์งœ๊ธฐ ์ „์— ๋ด์•ผํ•  ์œ ํŠœ๋ธŒ ๋ฐ์ดํ„ฐ API ๋งŒ๋“œ๋Š” ๋ฒ•

 

https://codebunny99.tistory.com/163

 

์œ ํŠœ๋ธŒ ๋ฐ์ดํ„ฐ API๋ฅผ Postman(ํฌ์ŠคํŠธ๋งจ)์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

https://console.cloud.google.com/welcome/new?hl=ko&project=eastern-surface-426200-r0&supportedpurview=project Google ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ๋กœ๊ทธ์ธ Google ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ์œผ๋กœ ์ด๋™accounts.google.com  youtube data ๊ฒ€์ƒ‰ํ•ด์„œ ์‚ฌ์šฉ ๋ˆ„๋ฅด

codebunny99.tistory.com

 

 

 

 

๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ

 

 

 

 

 

 

 

 

 

Config ๋งŒ๋“ค๊ธฐ

 

package com.~.youtube2.config;

public class Config {
    public static final String YOUTUBE_KEY = "์ž์‹ ์˜ key ์ž…๋ ฅ";
    public static final String DOMAIN = "https://www.googleapis.com";

}

 

 

 

 

 

NetworkClient ํด๋ž˜์Šค ์ž‘์„ฑํ•˜๊ธฐ

 

package com.~.youtube2.api;

import android.content.Context;

import com.~.youtube2.config.Config;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class NetworkClient {

    public static Retrofit retrofit;

    public static Retrofit getRetrofitClient(Context context){
        if(retrofit == null){
            // ํ†ต์‹  ๋กœ๊ทธ ํ™•์ธํ• ๋•Œ ํ•„์š”ํ•œ ์ฝ”๋“œ
            HttpLoggingInterceptor loggingInterceptor =
                    new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ๊ด€๋ จ ์ฝ”๋“œ
            OkHttpClient httpClient = new OkHttpClient.Builder()
                    .connectTimeout(1, TimeUnit.MINUTES)
                    .readTimeout(1, TimeUnit.MINUTES)
                    .writeTimeout(1, TimeUnit.MINUTES)
                    .addInterceptor(loggingInterceptor)
                    .build();
            // ๋„คํŠธ์›Œํฌ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๊ณ  ๋ฐ›๋Š”
            // ๋ ˆํŠธ๋กœํ• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ด€๋ จ ์ฝ”๋“œ
            retrofit = new Retrofit.Builder()
                    .baseUrl(Config.DOMAIN)
                    .client(httpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

 

 

 

 

 

VideoApi ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑํ•˜๊ธฐ

 

package com.~.youtube2.api;

import com.~.youtube2.model.VideoList;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface VideoApi {  //์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค.

    // ์œ ํŠœ๋ธŒ์— ๊ฒ€์ƒ‰ํ•˜๋Š” API

    // HTTP Method ์จ์ฃผ๊ณ , ๊ทธ ์•ˆ์—๋Š” ๊ฒฝ๋กœ๋ฅผ ์จ์ค€๋‹ค.
    @GET("/youtube/v3/search")
    Call<VideoList> searchVideo(@Query("key") String key,
                                @Query("part") String part,
                                @Query("maxResults") int maxResults,
                                @Query("order") String order,
                                @Query("type") String type,
                                @Query("q") String q );

    // ํ•จ์ˆ˜์˜ ๋ฆฌํ„ด ๋ฐ์ดํ„ฐ ํƒ€์ž…์€, Call ์•ˆ์— ์‘๋‹ต์œผ๋กœ ๋ฐ›์„ ํด๋ž˜์Šค๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

}

 

 

 

 

 

 

model ํŒจํ‚ค์ง€์— VideoList.class ์ž‘์„ฑํ•˜๊ธฐ

 

{ } ๋กœ ๋˜์–ด์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ํด๋ž˜์Šค๋กœ ์ž‘์„ฑํ•œ๋‹ค.

 

package com.~.youtube2.model;

import java.util.ArrayList;

public class VideoList {

    public String nextPageToken;
    public ArrayList<Item> items;
}

 

 

 

 

model ํŒจํ‚ค์ง€์— ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

 

 

ImageUrl ํด๋ž˜์Šค

package com.~.youtube2.model;

public class ImageUrl {

    public String url;

}

 

 

Item ํด๋ž˜์Šค

package com.~.youtube2.model;

public class Item {

    public Id id;
    public Video snippet;
}

 

 

Video ํด๋ž˜์Šค

package com.~.youtube2.model;

public class Video {

    public String title;
    public String description;
    public Thumbnail thumbnails;
}

 

 

Thumbnail ํด๋ž˜์Šค

package com.yujinoh.youtube2.model;

public class Thumbnail {

    public ImageUrl medium;
    public ImageUrl high;
}

 


Id ํด๋ž˜์Šค

package com.~.youtube2.model;

public class Id {

    public String videoId;

}

 

 

 

 

 

activity_main.xml ์ž‘์„ฑํ•˜๊ธฐ

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:id="@+id/topLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_margin="10dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/editSearch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dp"
                android:layout_weight="5"
                android:ems="10"
                android:hint="๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ..."
                android:inputType="text"
                android:textSize="22sp" />

            <ImageView
                android:id="@+id/imgSearch"
                android:layout_width="45dp"
                android:layout_height="45dp"
                app:srcCompat="@drawable/search_24dp" />
        </LinearLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/topLayout" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />
    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

 

 

activity_row.xml ๋งŒ๋“ค์–ด์ฃผ๊ธฐ

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="7dp"
        android:layout_marginRight="15dp"
        android:layout_marginBottom="7dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/txtTitle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:text="TextView"
                android:textSize="24sp" />

            <TextView
                android:id="@+id/txtDescription"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:layout_marginTop="7dp"
                android:layout_marginBottom="10dp"
                android:ellipsize="end"
                android:maxLines="2"
                android:text="TextView"
                android:textSize="20sp" />

            <ImageView
                android:id="@+id/imgThumb"
                android:layout_width="320dp"
                android:layout_height="180dp"
                android:layout_gravity="center"
                android:src="@drawable/image_24dp" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

 

 

 

 

 

MainActivity ์ž‘์„ฑํ•˜๊ธฐ

 

package com.~.youtube2;

import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

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

import com.~.youtube2.adapter.VideoAdapter;
import com.~.youtube2.api.NetworkClient;
import com.~.youtube2.api.VideoApi;
import com.~.youtube2.config.Config;
import com.~.youtube2.model.Item;
import com.~.youtube2.model.VideoList;

import java.util.ArrayList;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;


public class MainActivity extends AppCompatActivity {

    EditText editSearch;
    ImageView imgSearch;
    ProgressBar progressBar;
    RecyclerView recyclerView;

    ArrayList<Item> videoArrayList = new ArrayList<>();
    VideoAdapter adapter;

    String keyword;
    String nextPageToken;

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

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

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


        //ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” ์•ˆ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ
        progressBar.setVisibility(View.GONE);


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


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

                if (keyword.isEmpty()) {
                    Toast.makeText(MainActivity.this, "๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", Toast.LENGTH_SHORT).show();
                    return;
                }

                // ๋„คํŠธ์›Œํฌ๋กœ API๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค
                getNetworkData();


            }
        });
    }

    private void getNetworkData() {

        //ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ
        progressBar.setVisibility(View.VISIBLE);

        Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);

        VideoApi api = retrofit.create(VideoApi.class);

        // API๋ฅผ ๋งŒ๋“ค๊ณ 
        Call<VideoList> call =api.searchVideo(Config.YOUTUBE_KEY, "snippet", 20, "date", "video", keyword);

        // ๋„คํŠธ์›Œํฌ๋กœ ํ˜ธ์ถœํ•œ๋‹ค.
        call.enqueue(new Callback<VideoList>() {
            @Override
            public void onResponse(Call<VideoList> call, Response<VideoList> response) {
                // ๋„คํŠธ์›Œํฌ ํ†ต์‹  ์„ฑ๊ณต

                // ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” ์•ˆ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ
                progressBar.setVisibility(View.GONE);

                // 200 OK ์ผ๋•Œ ์ฝ”๋“œ
                if(response.isSuccessful()){

                    // ์‘๋‹ตํ•˜๋Š” ์ „์ฒด JSON ๋ฐ์ดํ„ฐ๋Š” body์— ๋“ค์–ด์žˆ์œผ๋ฏ€๋กœ body() ํ•จ์ˆ˜ ํ˜ธ์ถœํ•œ๋‹ค.
                    // Retrofit ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” JSON์„ ์ž๋ฐ”์˜ ํด๋ž˜์Šค๋กœ ๋ฐ”๋กœ ๋‹ด์•„์ฃผ๋ฏ€๋กœ ํŒŒ์‹ฑํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
                    VideoList videoList = response.body();

                    nextPageToken = videoList.nextPageToken;

                    videoArrayList.addAll( videoList.items );

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

                    recyclerView.setAdapter(adapter);


                }else if(response.code() == 400){
                    // response.code()๋Š” HTTP ์ƒํƒœ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.

                }else if(response.code() == 500){

                }else {

                }
            }

            @Override
            public void onFailure(Call<VideoList> call, Throwable throwable) {
                // ๋„คํŠธ์›Œํฌ ํ†ต์‹  ์‹คํŒจ

                // ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ” ์•ˆ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ
                progressBar.setVisibility(View.GONE);

                // ์œ ์ €ํ•œํ…Œ ์•Œ๋ ค์ฃผ๊ณ , ๋กœ๊ทธ์ฐ๊ณ  ๋ฆฌํ„ด
            }
        });

    }

}

 

 

 

 

 

Adapter ๋งŒ๋“ค๊ธฐ

 

package com.~.youtube2.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.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;


import com.~.youtube2.R;
import com.~.youtube2.model.Item;
import com.bumptech.glide.Glide;

import java.util.ArrayList;

public class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.ViewHolder>{

    Context context;
    ArrayList<Item> videoArrayList;

    public VideoAdapter(Context context, ArrayList<Item> videoArrayList) {
        this.context = context;
        this.videoArrayList = videoArrayList;
    }

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

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Item video = videoArrayList.get(position);

        holder.txtTitle.setText( video.snippet.title );
        holder.txtDescription.setText( video.snippet.description );
        Glide.with(context).load( video.snippet.thumbnails.medium.url ).into( holder.imgThumb );
    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView txtTitle;
        TextView txtDescription;
        ImageView imgThumb;
        CardView cardView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            txtTitle = itemView.findViewById(R.id.txtTitle);
            txtDescription = itemView.findViewById(R.id.txtDescription);
            imgThumb = itemView.findViewById(R.id.imgThumb);
            cardView = itemView.findViewById(R.id.cardView);


        }

        // ์›น๋ธŒ๋ผ์šฐ์ € ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ํ•จ์ˆ˜
        void openWebPage(String url){
            Uri uri = Uri.parse(url);
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            context.startActivity(intent);
        }
    }
}