레트로핏은 서버와의 HTTP 통신을 통해 전달된 데이터를 앱에서 특정 형태로 받아볼 수 있게 하는 라이브러리이다. 이 글은 영화진흥위원회의 Open API로부터 정보를 얻어와 출력하는 예제이다.
app 수준의 gradle에 가서 다음 내용을 추가해주자.
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
2020년 12월 19일 기준으로 최신 버전을 사용한 것이며, 최신 버전은 여기에서 확인할 수 있다.
<uses-permission android:name="android.permission.INTERNET" />
android:usesCleartextTraffic="true" // application 태그 안에
인터넷을 사용해야 되므로 인터넷 권한을 추가해주고 application 태그 안에 android:usesCleartextTraffic="true"를 추가해준다.
이 예제는 영화진흥위원회의 Open API를 사용할 것이다.
공공 데이터 포털같은 경우 활용 신청을 하고 승인이 나야 사용할 수 있지만, 위 사이트의 경우 회원가입을 하는 것만으로도 키를 발급받을 수 있다.
데이터를 받아올 껍데기 클래스인 Pojo Class를 만들어야 한다. 직접 작성해도 괜찮지만 이를 자동으로 만들어주는 사이트가 있다.
다음 링크로 들어가서 Json 파일의 내용을 복사해서 붙여넣기하고 Preview 버튼을 누르면 코드가 생성된다.
이 예제에서는 생성된 코드를 사용할 것이다.
package com.example.retrofit_practice1.retrofit;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class BoxOfficeResult {
@SerializedName("boxofficeType")
@Expose
private String boxofficeType;
@SerializedName("showRange")
@Expose
private String showRange;
@SerializedName("dailyBoxOfficeList")
@Expose
private List<DailyBoxOfficeList> dailyBoxOfficeList = null;
public String getBoxofficeType() {
return boxofficeType;
}
public void setBoxofficeType(String boxofficeType) {
this.boxofficeType = boxofficeType;
}
public String getShowRange() {
return showRange;
}
public void setShowRange(String showRange) {
this.showRange = showRange;
}
public List<DailyBoxOfficeList> getDailyBoxOfficeList() {
return dailyBoxOfficeList;
}
public void setDailyBoxOfficeList(List<DailyBoxOfficeList> dailyBoxOfficeList) {
this.dailyBoxOfficeList = dailyBoxOfficeList;
}
}
BoxOfficeResult.java
package com.example.retrofit_practice1.retrofit;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class DailyBoxOfficeList {
@SerializedName("rnum")
@Expose
private String rnum;
@SerializedName("rank")
@Expose
private String rank;
@SerializedName("rankInten")
@Expose
private String rankInten;
@SerializedName("rankOldAndNew")
@Expose
private String rankOldAndNew;
@SerializedName("movieCd")
@Expose
private String movieCd;
@SerializedName("movieNm")
@Expose
private String movieNm;
@SerializedName("openDt")
@Expose
private String openDt;
@SerializedName("salesAmt")
@Expose
private String salesAmt;
@SerializedName("salesShare")
@Expose
private String salesShare;
@SerializedName("salesInten")
@Expose
private String salesInten;
@SerializedName("salesChange")
@Expose
private String salesChange;
@SerializedName("salesAcc")
@Expose
private String salesAcc;
@SerializedName("audiCnt")
@Expose
private String audiCnt;
@SerializedName("audiInten")
@Expose
private String audiInten;
@SerializedName("audiChange")
@Expose
private String audiChange;
@SerializedName("audiAcc")
@Expose
private String audiAcc;
@SerializedName("scrnCnt")
@Expose
private String scrnCnt;
@SerializedName("showCnt")
@Expose
private String showCnt;
public String getRnum() {
return rnum;
}
public void setRnum(String rnum) {
this.rnum = rnum;
}
public String getRank() {
return rank;
}
public void setRank(String rank) {
this.rank = rank;
}
public String getRankInten() {
return rankInten;
}
public void setRankInten(String rankInten) {
this.rankInten = rankInten;
}
public String getRankOldAndNew() {
return rankOldAndNew;
}
public void setRankOldAndNew(String rankOldAndNew) {
this.rankOldAndNew = rankOldAndNew;
}
public String getMovieCd() {
return movieCd;
}
public void setMovieCd(String movieCd) {
this.movieCd = movieCd;
}
public String getMovieNm() {
return movieNm;
}
public void setMovieNm(String movieNm) {
this.movieNm = movieNm;
}
public String getOpenDt() {
return openDt;
}
public void setOpenDt(String openDt) {
this.openDt = openDt;
}
public String getSalesAmt() {
return salesAmt;
}
public void setSalesAmt(String salesAmt) {
this.salesAmt = salesAmt;
}
public String getSalesShare() {
return salesShare;
}
public void setSalesShare(String salesShare) {
this.salesShare = salesShare;
}
public String getSalesInten() {
return salesInten;
}
public void setSalesInten(String salesInten) {
this.salesInten = salesInten;
}
public String getSalesChange() {
return salesChange;
}
public void setSalesChange(String salesChange) {
this.salesChange = salesChange;
}
public String getSalesAcc() {
return salesAcc;
}
public void setSalesAcc(String salesAcc) {
this.salesAcc = salesAcc;
}
public String getAudiCnt() {
return audiCnt;
}
public void setAudiCnt(String audiCnt) {
this.audiCnt = audiCnt;
}
public String getAudiInten() {
return audiInten;
}
public void setAudiInten(String audiInten) {
this.audiInten = audiInten;
}
public String getAudiChange() {
return audiChange;
}
public void setAudiChange(String audiChange) {
this.audiChange = audiChange;
}
public String getAudiAcc() {
return audiAcc;
}
public void setAudiAcc(String audiAcc) {
this.audiAcc = audiAcc;
}
public String getScrnCnt() {
return scrnCnt;
}
public void setScrnCnt(String scrnCnt) {
this.scrnCnt = scrnCnt;
}
public String getShowCnt() {
return showCnt;
}
public void setShowCnt(String showCnt) {
this.showCnt = showCnt;
}
}
DailyBoxOfficeList.java
package com.example.retrofit_practice1.retrofit;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Result {
@SerializedName("boxOfficeResult")
@Expose
private BoxOfficeResult boxOfficeResult;
public BoxOfficeResult getBoxOfficeResult() {
return boxOfficeResult;
}
public void setBoxOfficeResult(BoxOfficeResult boxOfficeResult) {
this.boxOfficeResult = boxOfficeResult;
}
}
Result.java
두 가지 어노테이션을 볼 수 있는데
@SerialzedName : JSON으로 serialize 될 때 매칭되는 이름을 명시하는 목적으로 사용
@Expose : object 중 해당 값이 null일 경우, json으로 만들 필드를 자동 생략
의 의미를 가진다.
사이트에 있는 내용을 토대로 통신 인터페이스를 작성해주면 된다.
package com.example.retrofit_practice1.retrofit;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface RetrofitInterface {
@GET("http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
Call<Result> getBoxOffice(@Query("key") String key, @Query("targetDt") String targetDt);
}
RetrofitInterface.java
package com.example.retrofit_practice1.retrofit;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static RetrofitClient instance = null;
private static RetrofitInterface retrofitInterface;
private static String baseUrl = "http://www.kobis.or.kr";
private RetrofitClient() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofitInterface = retrofit.create(RetrofitInterface.class);
}
public static RetrofitClient getInstance() {
if (instance == null) {
instance = new RetrofitClient();
}
return instance;
}
public static RetrofitInterface getRetrofitInterface() {
return retrofitInterface;
}
}
RetrofitClient.java
baseUrl에는 말 그대로 주소를 넣으면 된다. Retrofit Client의 경우 싱글톤 패턴으로 구현하였다.
받아온 데이터를 화면에 보여주기 위해 리사이클러 뷰에 데이터를 뿌려주는 형태로 구현하였다.
rank(순위), movieNm(영화 이름), openDt(개봉일)만 표시하도록 하였다.
<?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="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="16dp"
app:cardElevation="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/rank"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:background="@color/purple_500"
android:gravity="center"
android:textColor="#FFFFFF"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/movieNM"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:textSize="18sp"
tools:text="movieName" />
<TextView
android:id="@+id/openDt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textSize="12sp"
tools:text="openDate" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
item.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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml
package com.example.retrofit_practice1;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.retrofit_practice1.retrofit.DailyBoxOfficeList;
import java.util.List;
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.ViewHolder>{
private List<DailyBoxOfficeList> items;
public MovieAdapter(List<DailyBoxOfficeList> items){
this.items = items;
}
@NonNull
@Override
public MovieAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item , parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
DailyBoxOfficeList item = items.get(position);
holder.setItem(item);
}
@Override
public int getItemCount() {
return items.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView rank, movieNm, openDt;
public ViewHolder(View itemView) {
super(itemView);
rank = itemView.findViewById(R.id.rank);
movieNm = itemView.findViewById(R.id.movieNM);
openDt = itemView.findViewById(R.id.openDt);
}
public void setItem(DailyBoxOfficeList item){
rank.setText(item.getRank());
movieNm.setText(item.getMovieNm());
openDt.setText(item.getOpenDt());
}
}
}
MovieAdapter.java
리사이클러뷰 안의 아이템의 레이아웃과 메인 액티비티의 레이아웃을 짜주고 리사이클러뷰의 어댑터를 만들어주었다.
package com.example.retrofit_practice1;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import com.example.retrofit_practice1.retrofit.BoxOfficeResult;
import com.example.retrofit_practice1.retrofit.Result;
import com.example.retrofit_practice1.retrofit.RetrofitClient;
import com.example.retrofit_practice1.retrofit.RetrofitInterface;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private RecyclerView.Adapter mAdapter;
private RetrofitClient retrofitClient;
private RetrofitInterface retrofitInterface;
private String API_KEY = "YOUR_KEY";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext(), LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
retrofitClient = RetrofitClient.getInstance();
retrofitInterface = RetrofitClient.getRetrofitInterface();
retrofitInterface.getBoxOffice(API_KEY, "20201201").enqueue(new Callback<Result>() {
@Override
public void onResponse(Call<Result> call, Response<Result> response) {
Result result = response.body();
BoxOfficeResult boxOfficeResult = result.getBoxOfficeResult();
Log.d("retrofit", "Data fetch success");
mAdapter = new MovieAdapter(boxOfficeResult.getDailyBoxOfficeList());
recyclerView.setAdapter(mAdapter);
}
@Override
public void onFailure(Call<Result> call, Throwable t) {
Log.d("retrofit", t.getMessage());
}
});
}
}
MainActivity.java
(YOUR_KEY 부분에 발급받은 키를 적으면 된다.)
앞서 만든 Retrofit Client로부터 Client와 Interface를 받아오도록 하자. 받아올 데이터가 이중으로 되어 있기 때문에, result 객체에 한 번 받아오고 그 안의 boxOfficeResult 객체를 리사이클러뷰 아답터에 전달해준다.
위는 2020년 12월 1일 자 기준 순위이며 다른 일자의 순위를 보고 싶다면 targetDt에 다른 일자를 넣으면 된다.
전체 소드 코드는 여기를 클릭하면 된다.
[Android Studio] Retrofit2 기본 사용법 / Retrofit 의문점 풀어헤치기 (스압), https://medium.com/@joycehong0524/android-studio-retrofit2-%EA%B8%B0%EB%B3%B8-%EC%82%AC%EC%9A%A9%EB%B2%95-retrofit-%EC%9D%98%EB%AC%B8%EC%A0%90-%ED%92%80%EC%96%B4%ED%97%A4%EC%B9%98%EA%B8%B0-%EC%8A%A4%EC%95%95-f150db436add
[안드로이드] Retrofit2 정리 예제, https://youngest-programming.tistory.com/77
Retrofit 문서, https://square.github.io/retrofit/2.x/retrofit/