안드로이드/정리(Android)

Android - RecyclerView와 Retrofit을 사용한 Api 불러오기

김염인 2022. 3. 4. 21:34

이번에 실습해본 내용은 Retrofit2을 이용하여 http://jsonplaceholder.typicode.com/photos 이곳에 있는 Api를 불러서 RecyclerView를 통해 데이터를 뿌려보았다.

 

1. 환경설정

Manifast Internet Permission 허용하기,

<uses-permission android:name="android.permission.INTERNET" />

app수준 buildgradle에 Okhttp, retrofit2, piccaso 권한 추가

implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc03"

implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-gson:2.7.2'

implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

여기서 picasso2는 https://~~ 형식으로 받아온 image 파일을 변환해주어 출력되도록 해주는 라이브러리이다.

 

2. 레이아웃 꾸미기

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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textViewEx"
        android:layout_width="wrap_content"
        android:layout_height="64dp"
        android:text="레트로핏 실습"
        android:textStyle="bold"
        android:textSize="28sp"
        android:gravity="center"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/viewEx"
        android:layout_width="match_parent"
        android:layout_height="2px"
        app:layout_constraintBottom_toBottomOf="@+id/textViewEx"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:background="@color/purple_200"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyClerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        tools:listitem="@layout/recycler_view"
        app:layout_constraintTop_toBottomOf="@id/viewEx"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:paddingTop="20px"
        />
</androidx.constraintlayout.widget.ConstraintLayout>

 

recycler_view.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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/frame_card"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.cardview.widget.CardView
            android:id="@+id/cardView"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            android:layout_margin="20dp"
            app:cardCornerRadius="10dp"
            app:cardElevation="20dp">

            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_weight="1"
                android:layout_gravity="center"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">


                <ImageView xmlns:app="http://schemas.android.com/apk/res-auto"
                    android:id="@+id/iv_poster"
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:layout_marginLeft="20dp"
                    android:layout_marginTop="20dp"
                    android:layout_marginBottom="20dp"
                    android:scaleType="centerCrop"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:background="@color/purple_200" />

                <TextView
                    android:id="@+id/tv_text"
                    android:layout_width="230dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="16dp"
                    android:text="벤자민 버튼의 시간은 거꾸로 간다."
                    android:textStyle="bold"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toEndOf="@+id/iv_poster"
                    app:layout_constraintTop_toTopOf="parent" />

                <TextView
                    android:id="@+id/tv_id"
                    android:layout_width="29dp"
                    android:layout_height="23dp"
                    android:layout_marginTop="12dp"
                    app:layout_constraintEnd_toEndOf="@+id/tv_text"
                    app:layout_constraintTop_toBottomOf="@+id/tv_text"
                    tools:text="9.9" />

            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.cardview.widget.CardView>

    </RelativeLayout>


</FrameLayout>
FrameLayout으로 감싼뒤 RelativeLayout에 cardview를 넣어준다. retrofi을 통해 api가 다 불러와지지 않을 경우를 대비해 progressbar를 통해 진행중 이라는 여부를 표시해준다.

 

완성된 레이아웃

3. Api 불러오기

이번 시간에 불러올 API는 http://jsonplaceholder.typicode.com/photos 이곳에서 가져와야 한다 총 500개의 Photo들이 있다.

 

ㅁ 위에서 보이는 json 모델들을 @SerializedName 를 통해 data class로 만들어준다.

package com.example.retrofitpractice.model


import com.google.gson.annotations.SerializedName

data class PhotoEntity(
    @SerializedName("albumId") val albumId: Int,
    @SerializedName("id") val id: Int,
    @SerializedName("thumbnailUrl") val thumbnailUrl: String,
    @SerializedName("title") val title: String,
    @SerializedName("url") val url: String
)

 

ㅁ InterFaceRetrofit을 만들어준다.

package com.example.retrofitpractice.model

import retrofit2.Call
import retrofit2.http.GET

interface InterFaceRetrofit {
    @GET("/photos")
    fun listPhotos(): Call<List<PhotoEntity>>
}

PhotoEntity를 List로 담아서 Call을 지정해준다.

 

 

ㅁ Retrofit 통신을 위한 retrofitApi Object를 만들어준다.

package com.example.retrofitpractice.model

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object retrofitApi {
    private const val BASE_URL = "https://jsonplaceholder.typicode.com/"

    private val retrofit : Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val retrofitService : InterFaceRetrofit by lazy {
        retrofit.create(InterFaceRetrofit::class.java)
    }
}

이렇게 object로 따로 만들어준 이유는 Singleton 패턴을 이용하여 필요할때 한번 가져올 수 있도록 해야하기 때문이다.

1) 상수형태의 BASE_URL을 설정해준다

2) retrofit을 lazy로 Builder()를 선언해준다.

3) retofitSerce로 InterFaceRetrofit을 가져온다.

 

이렇게 하면 Api불러오는 설정은 다 끝났다.

 

4. mainActivity로 api 뿌려주기

package com.example.retrofitpractice

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.retrofitpractice.adapter.RecyclerViewAdapter
import com.example.retrofitpractice.databinding.ActivityMainBinding
import com.example.retrofitpractice.model.InterFaceRetrofit
import com.example.retrofitpractice.model.PhotoEntity
import com.example.retrofitpractice.model.retrofitApi
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MainActivity : AppCompatActivity() {

    lateinit var adapter: RecyclerViewAdapter
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        adapter = RecyclerViewAdapter()


        getPhotosListFromServer()
        initRecyclerView()
    }

    private fun initRecyclerView() {
        binding.recyClerView.adapter = adapter
        binding.recyClerView.layoutManager = LinearLayoutManager(this)
    }


    private fun getPhotosListFromServer() {
        val service = retrofitApi.retrofitService

        service.listPhotos().enqueue(
            object : Callback<List<PhotoEntity>> {
                override fun onResponse(
                    call: Call<List<PhotoEntity>>,
                    response: Response<List<PhotoEntity>>
                ) {
                    if(response.isSuccessful == true){
                        val result = response.body()?.toList()
                        adapter.submitList(result!!)
                    }
                }

                override fun onFailure(call: Call<List<PhotoEntity>>, t: Throwable) {
                    Log.d("this", "실패 : ${t.toString()}")
                }

            }
        )

    }
}

InterFaseRetrofit에서 Call해준 <List<PohoEntity>>를 다시 callback 받아 response 해준다. 그리고 response.body()를 리스트에 담아서 oncreate에서 생성한 recyclerview adapter에 submitList를 통해 전달해준다.그러면 최종적으로 callback이 완료된다.

 

다음에는 공공데이터를 활용해 retrofit2을 불러오는 실습을 해봐야 겠다.