지난 포스트에서 RecyclerView Adapter를 만들어서 REST API에서 받은 데이터를 매핑해 주는 작업을 완료하였습니다. 이번 포스트에서는 본격적으로 KIS OpenAPI를 호출하여 Android App을 완성해 봅니다.
제가 올려놓은 유튜브 영상 참고하시면 보다 이해가 쉬우실 것입니다.
1. Main Activity 및 Toolbar
Main Activity에서 구현해야 할 기능은 대략 다음과 같습니다.
- Main Activity 기능 구현
- Retrofit Client 호출(fetchStock) 및 수신한 json Data Adapter Mapping
- 간단한 Toolbar 생성
2. Main Activity 기능 구현
이전 포스트에서 작성한 Class를 사용하기 위해서 adapter와 stock Arraylist를 선언해 줍니다.
StockAdapter adapter;
List<Stock> stockList = new ArrayList<>();
Main Activity OnCreate 할 때 이미 만들어준 RecyclerView Control을 찾아서 지난 포스트에서 만들어둔 StockAdapter를 Setting합니다. StockAdapter에는 Stock Array List를 매핑시켜 줍니다. 그런 이후 fetchStock이라는 Function을 통해 RetrofitClient를 호출합니다.
recyclerView = findViewById(R.id.recyclerview);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new StockAdapter(stockList);
recyclerView.setAdapter(adapter);
fetchStock();
3. fetchStock Function
fetchStock Function에서는 ApiInterface에서 정의해 둔 QueryMap에 Parameter를 담을 것입니다. HashMap을 이용해서 1번 Post에서 정리해둔 KIS OpenAPI Request Query Parameter 값들을 매핑시켜 줍니다.
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("authorization","Bearer 발급받은 나의 Access Token");
headers.put("appKey", "나의 appKey");
headers.put("appSecret","나의 appSecret Key");
headers.put("tr_id","FHPST01700000");
Map<String, String> map = new HashMap<>();
map.put("fid_cond_mrkt_div_code","J");
map.put("fid_cond_scr_div_code","20170");
map.put("fid_input_iscd","0000");
map.put("fid_rank_sort_cls_code","0");
map.put("fid_input_cnt_1","0");
map.put("fid_prc_cls_code","0");
map.put("fid_input_price_1","");
map.put("fid_input_price_2","");
map.put("fid_vol_cnt","");
map.put("fid_trgt_cls_code","0");
map.put("fid_trgt_exls_cls_code","0");
map.put("fid_div_cls_code","0");
map.put("fid_rsfl_rate1","");
map.put("fid_rsfl_rate2","");
그런 다음 Retrofit Client를 호출하는데, 이 때 Callback은 부모인 output으로 받아줘야 하므로 Ranking으로 받습니다.
RetrofitClient.getRetrofitClient().getStocks(headers, map).enqueue(new Callback<Ranking>()
Retrofit 호출이 정상적으로 이루어졌다면, Ranking Class에 json 데이터(response.body())가 들어왔고, Ranking Class의 getOutput Function을 호출하면 받은 Stock을 Arraylist로 던져줍니다. 이 값을 stockList에 받아서 adapter에 데이터가 들어왔다고 알려주면 끝입니다.
public void onResponse(Call<Ranking> call, Response<Ranking> response) {
Log.d(TAG, "ON Response : Server Response: " + response.toString());
Log.d(TAG, "ON Response : received Information: " + response.body().toString());
stockList.addAll(response.body().getOutput());
adapter.notifyDataSetChanged();
}
3. Toolbar 생성
UI 상단에 Toolbar를 하나 넣어주어 보기 좋게 App을 완성합니다. 먼저 예쁜 color를 만들기 위해서 values 폴더의 color.xml을 Open해서 라벤더 color를 하나 넣어줍니다.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="red">#E91E63</color>
<color name="lavenda">#8692f7</color>
</resources>
그 다음은 activity_main에서 recyclerview 위에 toolbar control을 넣어주고 RecyclerView와 겹치지 않게 간격을 벌려줍니다.
<?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">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="MegaBomb"
app:titleTextColor="@color/white"
android:background="@color/lavenda"
app:titleMarginStart="18dp"
tools:ignore="MissingConstraints"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="65dp"
app:layout_constraintStart_toStartOf="@id/toolbar"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java에서 위에서 만들어준 toolbar를 인식시켜 줍니다.
setSupportActionBar(findViewById(R.id.toolbar));
이제 전체 App 작성이 완료되었습니다. App을 Running시키면 애초 목표했던 국내주식 등락률 순위[v1_국내주식-088] 데이터를 정상적으로 수신하실 것입니다.
Android를 처음 접하시는 분들에게는 조금 어려우실 수도 있을 것 같아서 차후 코딩과정을 담은 동영상으로 로딩해 드리겠습니다.
[참고]
이 예제에서 사용한 Access Token은 Python을 미리 받아놓고 사용했으며, Token 발급 방법은 아래 Python 코드를 참고해 주시면 됩니다.
MainActivity.java 전체 소스코드입니다.
package com.example.megabomb;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
RecyclerView recyclerView;
LinearLayoutManager layoutManager;
StockAdapter adapter;
List<Stock> stockList = new ArrayList<>();
SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
setSupportActionBar(findViewById(R.id.toolbar));
recyclerView = findViewById(R.id.recyclerview);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new StockAdapter(stockList);
recyclerView.setAdapter(adapter);
fetchStock();
}
private void fetchStock() {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("authorization","Bearer 발급받은 나의 Access Token");
headers.put("appKey", "나의 appKey");
headers.put("appSecret","나의 appSecret Key");
headers.put("tr_id","FHPST01700000");
Map<String, String> map = new HashMap<>();
map.put("fid_cond_mrkt_div_code","J");
map.put("fid_cond_scr_div_code","20170");
map.put("fid_input_iscd","0000");
map.put("fid_rank_sort_cls_code","0");
map.put("fid_input_cnt_1","0");
map.put("fid_prc_cls_code","0");
map.put("fid_input_price_1","");
map.put("fid_input_price_2","");
map.put("fid_vol_cnt","");
map.put("fid_trgt_cls_code","0");
map.put("fid_trgt_exls_cls_code","0");
map.put("fid_div_cls_code","0");
map.put("fid_rsfl_rate1","");
map.put("fid_rsfl_rate2","");
RetrofitClient.getRetrofitClient().getStocks(headers,map).enqueue(new Callback<Ranking>() {
@Override
public void onResponse(Call<Ranking> call, Response<Ranking> response) {
Log.d(TAG, "ON Response : Server Response: " + response.toString());
Log.d(TAG, "ON Response : received Information: " + response.body().toString());
stockList.addAll(response.body().getOutput());
adapter.notifyDataSetChanged();
}
@Override
public void onFailure(Call<Ranking> call, Throwable throwable) {
Log.e(TAG, "Something went wrong " + throwable.getMessage());
}
});
}
}