[안드로이드 스튜디오] 환율 API 연동해서 환전 어플리케이션 만들기 - 1

2023. 2. 18. 23:35개발/안드로이드 스튜디오

결과물

시작 전)

먼저 환율 API를 쓰기 위해 해당 사이트에 가입한다.

https://app.freecurrencyapi.com/

 

freecurrencyapi

 

app.freecurrencyapi.com

월간 5000회의 데이터 요청이 무료이다. 우린 무료 플랜을 이용한다.

 

가입 후에 바로 이렇게 나온다.

사진 하단 API Key는 나중에 환율 요청할 때 url에 섞어서 보낼 것이다. 나중에 쓸 것이니 아직 복사할 필요가 없다.

 

시작)

 

1. Androidmanifest.xml

 

파일에 들어가서 몇줄 추가한다.

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

<application 태그 안에

android:usesCleartextTraffic="true"

를 추가한다. 인터넷 연결에 대한 권한 허용과 깨끗하게 한번 초기화 하는 구문인 듯 하다.

 

Androidmanifest.xml 전체 코드

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:usesCleartextTraffic="true"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MoneyExchanger"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:windowSoftInputMode="adjustResize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

2. build.gradle

 

dependencies 안에

implementation 'com.google.code.gson:gson:2.8.5'

다음과 같이 추가한다. Google에서 만든 json 편집 라이브러리라고 한다. 잘은 모른다.

 

3. activity_main.xml

 

스크롤 뷰 안에 콘스트레인트레이아웃을 넣고 그 안에 화면 구성요소를 넣었다.

스크롤 뷰를 최상단으로 선언한 것은 키보드가 올라올 때 레이아웃이 가려지지 않도록 하기 위함이었다.

하지만 레이아웃 자체를 화면 위쪽에 구성해서 키보드가 올라와도 차이가 없었다.

 

activity_main.xml 전체 코드

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sv_root"
    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"
    android:fillViewport="true"
    tools:context=".MainActivity">

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

        <Spinner
            android:id="@+id/spinner"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="152dp"
            android:minHeight="48dp"
            app:layout_constraintEnd_toStartOf="@+id/et_from"
            app:layout_constraintHorizontal_bias="0.666"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Spinner
            android:id="@+id/spinner2"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:minHeight="48dp"
            app:layout_constraintEnd_toEndOf="@+id/spinner"
            app:layout_constraintStart_toStartOf="@+id/spinner"
            app:layout_constraintTop_toBottomOf="@+id/spinner" />

        <EditText
            android:id="@+id/et_from"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="36dp"
            android:ems="10"
            android:hint="Input amount"
            android:inputType="textPersonName"
            android:minHeight="48dp"
            android:textAlignment="center"
            android:textSize="34sp"
            app:layout_constraintBottom_toBottomOf="@+id/spinner"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="@+id/spinner" />

        <TextView
            android:id="@+id/tv_to"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:text="0"
            android:textAlignment="center"
            android:textSize="34sp"
            app:layout_constraintBottom_toBottomOf="@+id/spinner2"
            app:layout_constraintEnd_toEndOf="@+id/et_from"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="@+id/et_from"
            app:layout_constraintTop_toTopOf="@+id/spinner2" />

        <Button
            android:id="@+id/btn_exchange"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:minWidth="32dp"
            android:text="exchange"
            app:layout_constraintEnd_toEndOf="@+id/tv_to"
            app:layout_constraintHorizontal_bias="0.466"
            app:layout_constraintStart_toStartOf="@+id/spinner2"
            app:layout_constraintTop_toBottomOf="@+id/spinner2" />

    </androidx.constraintlayout.widget.ConstraintLayout>


</ScrollView>

콘스트레인트 레이아웃 안에 Spinner 2개, TextView 1개, EditText 1개, Button 1개를 선언해주었다.

각각의 Spinner는 MainActivity에서 선언된 배열과 어댑터를 통해 내용이 붙는다. 화폐 종류를 선택하는 역할을 한다.

EditText는 내가 환율을 확인할 액수를 입력하는 공간이다.

이후 Button은 환전하는 기능을 하고 TextView에 결과 금액이 나온다.

 

4. Task.java

 

API와 연동하는 부분의 클래스이다. 위에 말한 API key 가 필요하다.

clientKey 선언부에 API key를 넣자.

첫번째 try-catch문에서는 Json 데이터를 얻어낸 뒤 string 값으로 반환한다.

두번째 try-catch문에서는 string값을 jsonParser를 통해 원하는 값(환율)만 얻어낸다.

나중에 완성후 빌드할 때 중간에 있는 로그가 로그캣에 각 화폐의 환율과 교환비를 찍어줄 것이다.

여기서 요청은 1 USD를 기준으로 한 다른 화폐의 액수가 나온다.

 

Task.java 전체 코드

package com.example.moneyexchanger;

import android.os.AsyncTask;
import android.util.Log;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class Task extends AsyncTask<String, Void, Double> {

    String clientKey = "your API key in here";;
    private String str, receiveMsg;
    double currencyRate;


    // MainActivity에서 execute 의 인자로 string 배열을 넣고 여기서 params로 받는다.
    @Override
    protected Double doInBackground(String... params) {
        String[] resultArr = new String[3];

        String from = params[0];
        String to = params[1];
        URL url = null;
        try {
            url = new URL("https://api.freecurrencyapi.com/v1/latest?apikey="
                    +clientKey
                    +"&currencies="
                    +from
                    +"," + to);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");

            if (conn.getResponseCode() == conn.HTTP_OK) {
                InputStreamReader tmp = new InputStreamReader(conn.getInputStream(), "UTF-8");
                BufferedReader reader = new BufferedReader(tmp);
                StringBuffer buffer = new StringBuffer();
                while ((str = reader.readLine()) != null) {
                    buffer.append(str);
                }
                receiveMsg = buffer.toString();
                Log.e("receiveMsg : ", receiveMsg);

                reader.close();
            } else {
                Log.e("통신 결과", conn.getResponseCode() + "에러");
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        try {
            JsonParser jsonParser = new JsonParser();
            Object obj = jsonParser.parse(receiveMsg);

            JsonObject jsonObj = (JsonObject) obj;

            JsonObject curobj = (JsonObject) jsonObj.get("data");

            Log.e("parse", from + " " +  curobj.get(from).toString());
            Log.e("parse", to + " " + curobj.get(to).toString());
            String a = curobj.get(from).toString();
            String b = curobj.get(to).toString();

            currencyRate = Double.parseDouble(b)/Double.parseDouble(a);
            //double result = Math.round(currencyRate * 100.0) / 100.0;
            Log.e("parse", "currencyRate " + currencyRate);

            //double result = Math.round(input * currencyRate * 100.0) / 100.0;

        }catch (JsonParseException e){
            e.printStackTrace();
        }
        return currencyRate;
    }
}

 

5. MainActivity.java

 

화폐 단위를 입력해둔 문자열 배열을 선언하고 어댑터를 통해 Spinner와 연결시켰다.

어댑터 설정 중에 뒤에 파라미터로 오는 simple_spinner_dropdown.. 은 스튜디오에서 제공하는 레이아웃인 듯 싶다.

 

Spinner에서 무언가가 선택되면 fromto 배열에 담긴다. 첫번째 Spinner에서 바꿀 화폐의 단위, 두번째 Spinner에서 바꾸고 난 뒤의 화폐의 단위가 선택된다. 이것이 fromto배열에 담겨 task.execute() 의 인자가 된다. 

 

fromto 배열은 위의 Task 클래스의 doInBackground의 params가 된다.

params 앞의 String...은 여러 문자열 객체가 들어오는 것을 받는다는 뜻 같다. String[] 과 무엇이 다른진 모르겠다.

params의 두개의 화폐단위는 요청 url의 일부분이 된다.

 

Task의 doInBackground에서 반환한 currencyRate는 기존의 EditText et_from 의 값과 계산하여 TextView tv_to에 값을 출력한다.

 

MainActivity.java 전체 코드

package com.example.moneyexchanger;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;

import org.json.JSONException;

import java.text.ParseException;
import java.util.concurrent.ExecutionException;

public class MainActivity extends AppCompatActivity {
    private String[] currencyList = {"KRW", "USD", "EUR", "CAD"};
    public TextView et_from;
    public TextView tv_to;
    private Button btn_exchange;
    private String[] fromto = new String[2];

    private TextView tv_test;

    double currencyRate = 0.0;

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

        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        Spinner spinner = (Spinner) findViewById(R.id.spinner);
        Spinner spinner2 = findViewById(R.id.spinner2);

        et_from = findViewById(R.id.et_from);
        tv_to = findViewById(R.id.tv_to);

        btn_exchange = findViewById(R.id.btn_exchange);


        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
                androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, 
                currencyList);
        adapter.setDropDownViewResource(androidx.appcompat.R.layout.support_simple_spinner_dropdown_item);
        spinner.setAdapter(adapter);

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                fromto[0] = currencyList[i];
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

        spinner2.setAdapter(adapter);
        spinner2.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                fromto[1] = currencyList[i];
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });

        btn_exchange.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    // doInBackground의 인자로 들어갈 fromto 배열을 execute 에 넣어준다.
                    currencyRate = new Task().execute(fromto).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }

                double input = Double.parseDouble(et_from.getText().toString());
                double result = Math.round(input * currencyRate * 100.0) / 100.0; // 소수점 두자리에서 자르기 위함

                tv_to.setText(Double.toString(result));


            }
        });
    }
}

 

마치며..

이제 약간의 예외 처리와 기능 추가 및 UI 작업이 필요하다.

다음 화에서 다루고 싶다.