본문 바로가기
코딩일기/날씨앱 만들기 프로젝트

날씨 앱 만들기 : 기상청 api 사용하기( Service 키 인증 실패 오류)

by 욱파이어니어 2021. 9. 7.
728x90
반응형

원래 기존에 사용하려던 OpenWeatherMap api에서 해당 위치의 날씨 정보를 알아오려면
request parameter를 도시명으로 호출해야지만 날씨정보를 가져오게 설계되어 있었다.

나는 현재 기기의 위치에 따라서 날씨정보를 받아오고 싶었는데 OpenWeatherMap api를 사용하려면
해당 위치정보의 주소를 알수 있는 api를 호출해야하면 심지어 그 주소는 영문주소여야 했다.

따라서 나는 이 번거로움을 피하기 위해서 다른 api 를 물색했고 공공데이터 포털에서 제공해주는 단기예보 api 였다.


해당 api 의 사용법은 의외로 간단했다.

일단 공공 데이터 포털에서 회원가입을 한다.
https://www.data.go.kr/index.do


그리고 좌측 상단에 있는 아래 사진 부분을 클릭해서 들어간 다음



아래와 같이 검색해서 들어가서 api를 들어간다.




그리고 들어가고 나서는 활용신청을 해주면 된다.



활용 신청을 하고 나서 몇시간을 기다려야 된다고 하지만 나는 10분 정도 기다렸다가 postman을 통해서
호출을 해보니 잘 되었다.


이제 기존에 OpenWeatherMap api에서 했던때와 같이 Retrofit을 사용하여 똑같이 만들어줬다.
https://wpioneer.tistory.com/187

 

날씨 앱 만들기 : Retrofit을 사용하여 날씨 API 호출하기 1(Java / MVVM)

날씨 앱을 만들기에 앞서 일단은 날씨 API를 호출해서 데이터를 받아오는 작업을 먼저하기로 했다. 내가 사용할 API는 OpenWeathermap api이다. 해당 API를 사용하는 방법에 관해서는 아래 링크를 통해

wpioneer.tistory.com


나의 소스는 아래와 같다.


MeteorologicalAgencyAPI interface

package wook.co.weather.interfaces;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import wook.co.weather.models.dto.ShortWeather;

public interface MeteorologicalAgencyAPI {
    @GET("getVilageFcst")
    Call<ShortWeather> getShortWeather(@Query("serviceKey") String serviceKey,@Query("numOfRows") int numOfRows,@Query("pageNo") int pageNo,
                                       @Query("dataType") String dataType, @Query("base_date") String base_date, @Query("base_time") String base_time,
                                       @Query("nx") int nx,@Query("ny") int ny);

}



ShortWeather class

package wook.co.weather.models.dto;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;

public class ShortWeather implements Serializable {
	@Expose
	@SerializedName("response")
	private Response response;

	public static class Response implements Serializable {
		@Expose
		@SerializedName("body")
		private Body body;
		@Expose
		@SerializedName("header")
		private Header header;

		public Body getBody() {
			return body;
		}

		public Header getHeader() {
			return header;
		}

		@Override
		public String toString() {
			return "Response{" + "body=" + body + ", header=" + header + '}';
		}
	}

	public static class Body implements Serializable {
		@Expose
		@SerializedName("totalCount")
		private int totalcount;
		@Expose
		@SerializedName("numOfRows")
		private int numofrows;
		@Expose
		@SerializedName("pageNo")
		private int pageno;
		@Expose
		@SerializedName("items")
		private Items items;
		@Expose
		@SerializedName("dataType")
		private String datatype;

		public int getTotalcount() {
			return totalcount;
		}

		public int getNumofrows() {
			return numofrows;
		}

		public int getPageno() {
			return pageno;
		}

		public Items getItems() {
			return items;
		}

		public String getDatatype() {
			return datatype;
		}

		@Override
		public String toString() {
			return "Body{" + "totalcount=" + totalcount + ", numofrows=" + numofrows + ", pageno=" + pageno + ", items="
					+ items + ", datatype='" + datatype + '\'' + '}';
		}
	}

	public static class Items implements Serializable {
		@Expose
		@SerializedName("item")
		private List<Item> item;

		public List<Item> getItem() {
			return item;
		}

		@Override
		public String toString() {
			return "Items{" + "item=" + item + '}';
		}
	}

	public static class Item implements Serializable {
		@Expose
		@SerializedName("ny")
		private int ny;
		@Expose
		@SerializedName("nx")
		private int nx;
		@Expose
		@SerializedName("fcstValue")
		private String fcstvalue;
		@Expose
		@SerializedName("fcstTime")
		private String fcsttime;
		@Expose
		@SerializedName("fcstDate")
		private String fcstdate;
		@Expose
		@SerializedName("category")
		private String category;
		@Expose
		@SerializedName("baseTime")
		private String basetime;
		@Expose
		@SerializedName("baseDate")
		private String basedate;

		public int getNy() {
			return ny;
		}

		public int getNx() {
			return nx;
		}

		public String getFcstvalue() {
			return fcstvalue;
		}

		public String getFcsttime() {
			return fcsttime;
		}

		public String getFcstdate() {
			return fcstdate;
		}

		public String getCategory() {
			return category;
		}

		public String getBasetime() {
			return basetime;
		}

		public String getBasedate() {
			return basedate;
		}

		@Override
		public String toString() {
			return "Item{" + "ny=" + ny + ", nx=" + nx + ", fcstvalue='" + fcstvalue + '\'' + ", fcsttime='" + fcsttime
					+ '\'' + ", fcstdate='" + fcstdate + '\'' + ", category='" + category + '\'' + ", basetime='"
					+ basetime + '\'' + ", basedate='" + basedate + '\'' + '}';
		}
	}

	public static class Header implements Serializable {
		@Expose
		@SerializedName("resultMsg")
		private String resultmsg;
		@Expose
		@SerializedName("resultCode")
		private String resultcode;

		public String getResultmsg() {
			return resultmsg;
		}

		public String getResultcode() {
			return resultcode;
		}

		@Override
		public String toString() {
			return "Header{" + "resultmsg='" + resultmsg + '\'' + ", resultcode='" + resultcode + '\'' + '}';
		}
	}

	@Override
	public String toString() {
		return "ShortWeather{" + "response=" + response + '}';
	}
}


SplashActivity class ( ViewModel을 통해서 api 호출하는 Activity)

package wook.co.weather.view.splash;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

import wook.co.weather.R;
import wook.co.weather.models.dto.GeoInfo;
import wook.co.weather.models.dto.GpsTransfer;
import wook.co.weather.models.dto.ShortWeather;
import wook.co.weather.models.repository.MAgencyRepo;
import wook.co.weather.view.MainActivity;
import wook.co.weather.viewmodels.MAgencyViewModel;

public class SplashActivity extends AppCompatActivity{


    private final String TAG = "SplashActivity";
    private final int DEFAULT_LOCATION_REQUEST_PRIORITY = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; //배터리와 정확도를 밸런스있게 맞춰주는애
    private final long DEFAULT_LOCATION_REQUEST_INTERVAL = 2000L;
    private final long DEFAULT_LOCATION_REQUEST_FAST_INTERVAL = 10000L;

    private LocationRequest lr; //위치정보를 사용하기 위해서 사용하는 변수
    private ShortWeather sw;
    private MAgencyViewModel mavm; //ViewModel 변수


    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.splash_screen);
        //MAgencyViewModel 객체 생성
        mavm = new ViewModelProvider(this).get(MAgencyViewModel.class);
        //LocationRequest 객체 생성
        lr = LocationRequest.create();
        if(isNetworkAvailable(this)){
            checkLocationPermission();
        }else{
            AlertDialog.Builder tryAgain = new AlertDialog.Builder(this);
            tryAgain.setMessage("서버와 연결이 불안정합니다.\n 다시 시도해 주세요");
            tryAgain.setPositiveButton("종료", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    finish();
                }
            });
            tryAgain.show();
        }
    }

    public static boolean isNetworkAvailable(Context context) {
        if(context == null)  return false;

        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); //ConnectivityManager 객체 생성

        if (connectivityManager != null) {

            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
                if (capabilities != null) {
                    //네트워크 연결되어 있는지 확인
                    if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
                        Log.i("Check InterNet","Internet connection Available");
                        return true;
                    }
                }
            }
            else { //특정 버전 이하라면 아래진입
                try {
                    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); //네트워크 연결 정보 받아옴
                    if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) { //네트워크 연결되어 있다면 진입
                        Log.i("Check InterNet","Internet connection Available");
                        return true;
                    }
                } catch (Exception e) {
                    Log.i("Check InterNet","No internet connection");
                }
            }
        }
        Log.i("Check InterNet","No internet connection");
        return false;
    }

    public void checkLocationPermission(){
        //위치정보 권한 허용되어 있는지 아닌지를 확인하는 부분
        if(ActivityCompat.checkSelfPermission(SplashActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            //위치정보 권한 허용되어 있지 않다면 실행하는 코드, 여기서 request에 대한 응답이 나오면 아래에 있는 onRequestPermissionsResult() 함수를 콜백하게 됨
            ActivityCompat.requestPermissions(SplashActivity.this,new String[]{ Manifest.permission.ACCESS_FINE_LOCATION},200); //위치정보 권한을 요청한다.
        }else {
            //위치정보 권한이 허용되어 있을때 실행하는 코드
            Log.d(TAG, "위치정보 허용됨");
            //구글 플레이 서비스 위치정보 권한 승인된건지 확인해야함
            checkLocationSetting();
        }
    }

    //권한요청되었을때 콜백되어지는 함수 -> 이부분은 ViewModel로 옮겨져야 함
    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if(requestCode == 200){ //permissionCode가 200이고
            if(grantResults[0] == 0){ // 그중 가장 첫번째 result가 0 즉 승인된경우 진입
                Toast.makeText(getApplicationContext(),"위치정보 권한 승인됨",Toast.LENGTH_SHORT).show(); //위치정보 승인됐다고 알리고
                //위치정보 권한을 받았다면 진입
                if(ActivityCompat.checkSelfPermission(SplashActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){

                    //구글 플레이 서비스 위치정보 권한 승인된건지 확인해야함
                    checkLocationSetting();
                }
            }else{ //위치정보를 허가받지 못했을경우 진입
                Toast.makeText(getApplicationContext(),"위치정보를 승인하지 않으면 현재위치 기반으로 \n날씨정보를 알려드릴수 없습니다.",Toast.LENGTH_LONG).show();
                Log.i(TAG,"위치정보 권한 없음 : 위치정보를 허가 안해줌");

                //허가를 받지 못했을때의 결과를 받았을때 ViewModel의 메소드를 호출하면됨
                mavm.defaultLocation();// I call ViewModel at this part - This works
                obeserveAPI();
            }
        }
    }

    public void checkLocationSetting() {
        Log.d(TAG,"LocationReqeust is in setting");
        lr.setPriority(DEFAULT_LOCATION_REQUEST_PRIORITY);
        lr.setInterval(DEFAULT_LOCATION_REQUEST_INTERVAL);
        lr.setFastestInterval(DEFAULT_LOCATION_REQUEST_FAST_INTERVAL);

        SettingsClient settingsClient = LocationServices.getSettingsClient(this);
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(lr).setAlwaysShow(true);
        settingsClient.checkLocationSettings(builder.build())
                .addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
                    @Override
                    public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
                        //구글플레이 위치 서비스 사용할수 있게 됐을때 진입
                        mavm.requestUpdate(lr);
                        observeGps();
                    }
                })
                .addOnFailureListener(SplashActivity.this, new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        int statusCode = ((ApiException) e).getStatusCode();
                        switch (statusCode){
                            case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: //충분히 설정 변경하는것으로 변경이 가능할떄
                                ResolvableApiException rae = (ResolvableApiException) e;
                                try {
                                    rae.startResolutionForResult(SplashActivity.this, 200);
                                } catch (IntentSender.SendIntentException ex) {
                                    Log.w(TAG,"LocationService approval canceled");
                                }
                                break;
                            case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: //GPS가 아예 없거나 연결자체가 불가능하여 사용을 물리적으로 사용하지 못할때
                                Log.w(TAG,"No way to change setting");
                                Toast.makeText(getApplicationContext(),"GPS 사용을 하지 못하여 위치정보를 받아오지 못하고 있습니다.\n GPS 연결을 해주세요.",
                                        Toast.LENGTH_SHORT).show();
                                mavm.defaultLocation();
                                obeserveAPI();
                                break;
                        }
                    }
                });
    }

    @Override
    public void onActivityResult(int requstCode,int resultCode,Intent data) {

        super.onActivityResult(requstCode, resultCode, data);
        if(requstCode == 200) {
            if (resultCode == RESULT_OK) {
                checkLocationSetting();
            } else {
                Toast.makeText(getApplicationContext(), "위치정보를 승인하지 않으면 현재위치 기반으로 \n날씨정보를 알려드릴수 없습니다.", Toast.LENGTH_LONG).show();
                Log.i(TAG, "구글 플레이 : 위치정보를 허가 안해줌");

                //허가를 받지 못했을때의 결과를 받았을때 ViewModel의 메소드를 호출하면됨
                mavm.defaultLocation();// I call ViewModel at this part - This works
                obeserveAPI();
            }
        }
    }

    public void obeserveAPI(){
        mavm.getWeather().observe(this, new Observer<ShortWeather>() {
            @Override
            public void onChanged(ShortWeather shortWeather) {
                sw = mavm.getWeather().getValue();
                Log.i(TAG,sw.toString());

                Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //intent 형성한다.
                        Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);//액티비티 스택제거
                        //해당 intent에 객체를 실어서 보낸다.
                        intent.putExtra("shortWeather",sw);
                        startActivity(intent);
                    }
                },2000);
            }
        });
    }
    public void observeGps(){
        mavm.getGeo().observe(this, new Observer<GeoInfo>() {
            @Override
            public void onChanged(GeoInfo geoInfo) {
                Log.i(TAG,mavm.getGeo().getValue().toString());
                mavm.callApi(mavm.getGeo().getValue());
                obeserveAPI();
            }
        });
    }
}


MAgencyViewModel class

package wook.co.weather.viewmodels;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;

import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.OnSuccessListener;

import java.text.SimpleDateFormat;
import java.util.Calendar;

import wook.co.weather.models.dto.Coord;
import wook.co.weather.models.dto.GeoInfo;
import wook.co.weather.models.dto.GpsTransfer;
import wook.co.weather.models.dto.ShortWeather;
import wook.co.weather.models.repository.MAgencyRepo;
import wook.co.weather.view.MainActivity;
import wook.co.weather.view.splash.SplashActivity;

public class MAgencyViewModel extends AndroidViewModel { //ViewModel과 AndroidViewModel의 차이점은 Application의 유무이다.

    private final String TAG = "MAgencyViewModel";

    //이 클래스에서는 Model과 통신하여서 날씨 정보를 받아온다.
    private MutableLiveData<ShortWeather> sw;
    private MutableLiveData<GeoInfo> mldGi;
    private MAgencyRepo maRepo;
    private GeoInfo gi;
    private GpsTransfer gpt;

    private LocationCallback lcb;
    private LocationRequest lr;
    public boolean requestLocationUpdate;

    public MAgencyViewModel(@NonNull Application application) {
        super(application);
        Log.d(TAG,"LocationCallBack instance have been made");
        //LocationCallBack 부분 객체 생성하고 그에 LocatinResult 받았을때를 추상화 시켜주는 부분
        gpt = new GpsTransfer();
        lcb = new LocationCallback(){
            @Override
            public void onLocationResult(LocationResult locationResult){
                if(locationResult == null){
                    Log.d(TAG,"Location information have not been recieved");
                    return;
                }
                Log.d(TAG,"Location information have been recieved");
                //gps를 통하여서 위도와 경도를 입력받는다.
                for (Location location : locationResult.getLocations()) {
                    if (location != null) {
                        gpt.setLon(location.getLongitude());
                        gpt.setLat(location.getLatitude());
                    }
                }

                //gps 연결을 닫는다.
                LocationServices.getFusedLocationProviderClient(getApplication()).removeLocationUpdates(lcb);

                //x,y 좌표로 변환
                gpt.transfer(gpt,0);
                Log.d(TAG, gpt.toString());
                setGeoInfo(gpt); //변환된 정보를 GeoInfo에 넣음
                mldGi.setValue(gi);
            }
        };
    }

    @SuppressLint("MissingPermission")//위치권한 체크안해도 된다고 하는 부분 안하는 이유는 SplashActivity에서 이미 했기 때문이다.
    public void requestUpdate(LocationRequest locationRequest){

        Log.d(TAG,"LocationRequest have been request");
        mldGi = new MutableLiveData<GeoInfo>();
        requestLocationUpdate = true;
        LocationServices.getFusedLocationProviderClient(getApplication())
                .requestLocationUpdates(locationRequest,lcb,null);
    }


    //위치 정보 이용 권한 허가를 받지 못했을떄 호출 하는 부분
    public void defaultLocation() {

        //GpsTransfer 객체 생성
        gpt = new GpsTransfer();

        //GpsTransfer 위도와 경도를 원주로 설정
        gpt.setxLat(76);
        gpt.setyLon(122);
        gpt.transfer(gpt, 1);

        setGeoInfo(gpt);
        callApi(gi);

    }



    public void setGeoInfo(GpsTransfer gpt){
        gi = new GeoInfo();
        gi.setLon(gpt.getyLon());
        gi.setLat(gpt.getxLat());
        getTime();
    }

    public void callApi(GeoInfo geoInfo){
        if (sw != null) {
            return;
        }
        //해당 정보를 API를 호출
        maRepo = MAgencyRepo.getInStance();
        sw = maRepo.getWeather(geoInfo); // this part is calling a weather api
        Log.i(TAG, "API Connection finish");
    }
//머징

    public void getTime() {

        SimpleDateFormat dateSdf = new SimpleDateFormat("yyyyMMdd"); //년월일 받아오는 부분
        SimpleDateFormat timeSdf = new SimpleDateFormat("HH"); //현재시간 받아오는 부분

        Calendar cal = Calendar.getInstance(); //현재시간을 받아온다.

        gi.setNowDate(dateSdf.format(cal.getTime())); //날짜 세팅
        gi.setNowTime(timeSdf.format(cal.getTime())); //시간 세팅

        /*
         * 하루 전체의 기상예보를 받아오려면 전날 23시에 266개의 날씨정보를 호출해와야 한다.
         * 따라서 호출 값은 현재 날짜보다 1일전으로 세팅해줘야 한다.
         * */

        cal.add(Calendar.DATE, -1); //1일전 날짜를 구하기 위해 현재 날짜에서 -1 시켜주는 부분
        gi.setCallDate(dateSdf.format(cal.getTime())); //1일전 값으로 호출값 생성


        Log.i(TAG, "DATE : " + gi.getNowDate());
        Log.i(TAG, "TIME : " + gi.getNowTime());
        Log.i(TAG, "CALL DATE : " + gi.getCallDate());

    }

    public LiveData<ShortWeather> getWeather() {
        return sw;
    }
    public LiveData<GeoInfo> getGeo(){ return mldGi; }
}


MAgencyRepo class

package wook.co.weather.models.repository;

import android.util.Log;

import androidx.lifecycle.MutableLiveData;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import wook.co.weather.BuildConfig;
import wook.co.weather.interfaces.MeteorologicalAgencyAPI;
import wook.co.weather.models.dto.GeoInfo;
import wook.co.weather.models.dto.GpsTransfer;
import wook.co.weather.models.dto.ShortWeather;
import wook.co.weather.models.retrofit.RetrofitService;

public class MAgencyRepo {

    //이 클래스에서는 API 통신을 통해서 데이터를 가져와야 한다.
    private final String TAG = "MAgencyRepo";
    private final static String BASE_URL = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/"; //api의 baseURL을 상수로 적어서 올려놨다.
    private static MAgencyRepo instance;
    private Retrofit retrofit;
    private MeteorologicalAgencyAPI MaAPI;
    private ShortWeather sw;

    //OpenWeatherRepos 인스턴스 반환
    public static MAgencyRepo getInStance() {
        if(instance == null){
            instance = new MAgencyRepo();
        }
        return instance;
    }

    //날씨 정보를 직접적으러 받아와야 하는 부분
    public MutableLiveData<ShortWeather> getWeather(GeoInfo gi) {

        //Retrofit 객체 생성 생성
        retrofit = new RetrofitService().getRetroInstance(BASE_URL);

        //인터페이스 객체 생성
        MaAPI = retrofit.create(MeteorologicalAgencyAPI.class);

        //API와 통신을 하는 함수 호출
        sw = new ShortWeather();
        MutableLiveData<ShortWeather> data = new MutableLiveData<ShortWeather>();
        callWeatherAPI(data, gi);
        return data;
    }

    private void callWeatherAPI(MutableLiveData<ShortWeather> data, GeoInfo gi) {

        int nx = (int) gi.getLat();
        int ny = (int) gi.getLon();

        String baseDate = gi.getCallDate();
        String apiKey = BuildConfig.MAGENCY_API_KEY; //BuildGradle에서 만든 상수 APIKey를 가져옴

        //응답을 하고 받아오는 부분
        Call<ShortWeather> call = MaAPI.getShortWeather(apiKey, 266,1,"JSON",baseDate,"2300",nx,ny);

        call.enqueue(new Callback<ShortWeather>() {
            @Override
            public void onResponse(Call<ShortWeather> call, Response<ShortWeather> response) {
                if(response.isSuccessful()){ //연결이 성공적으로 되었을때 진입하는 부분
                    //해당 api는 오류가 생기더라도 여기로 오고 대신에 들어고는 resultCode값에 따라서 오류인것과 아닌것을 나눈다.
                    data.postValue(response.body());
                    Log.i(TAG,"API CONNECT SUCCESS");
                    Log.i(TAG,response.body().toString());
                }
            }

            //인증키 관련해서 잘못 된 부분만 onFailure에 오게 됨
            @Override
            public void onFailure(Call<ShortWeather> call, Throwable t) {
                Log.d(TAG,"onFailure : "+t.getMessage());
            }
        });
    }

}



RetrofitService class

public class RetrofitService { public Retrofit getRetroInstance(String baseUrl){ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); Gson gson = new GsonBuilder() .setLenient() .create(); return new Retrofit.Builder() .baseUrl(baseUrl) .client(client) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); } }



이렇게 사용해서 api 호출을 했는데 순서를 보자면

SplashActivity -> MAgencyViewModel -> MAgencyRepo -> RetrofitService -> MeteorologicalAgencyAPI

이런식으로 호출이 된다.


위와 같이 소스를 만들고 api를 호출하면 http 보안때문에 api를 호출하지 못한다는 에러가 아래처럼 뜰것이다.

onFailure : CLEARTEXT communication to apis.data.go.kr not permitted by network security policy


그럴땐 아래와 같이 manifext 파일에 application 부분에 아래 소스를 추가해주면 된다.

android:usesCleartextTraffic="true"


그럼 이제 아래와 같이 정상 호출이 될것이다.

내가 처음에 api를 호출했을때는 정상호출이 되지 않았다.

그 이유는 내가 api를 호출 했을때는 RetrofitService class 의 소스가 아래와 같았기때문이다.

public class RetrofitService {
	public Retrofit getRetroInstance(String baseUrl){ 
    	return new Retrofit.Builder() 
        .baseUrl(baseUrl) 
        .addConverterFactory(GsonConverterFactory.create()) 
        .build(); 
    }
}


이렇게하고 호출을 하니 아래와 같은 결과가 나왔다.

onFailure : Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path


라는 에러가 생겼었다.

위의 에러를 구글링 해보니 내가 gson 객체를 만들어 놓지 않고 api를 호출했기때문이라고 해서 아래와 같이 변경을 해주었다.

그러고나서의 RetrofitService 코드는 아래와 같았다.

public class RetrofitService { 
	public Retrofit getRetroInstance(String baseUrl){ 
    	Gson gson = new GsonBuilder() 
        .setLenient() 
        .create(); 
        return new Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(GsonConverterFactory.create(gson)).build(); 
    } 
}


이렇게 설정을 하고 api를 호출해보니 아래와 같은 에러를 받았다.

onFailure : java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $


위 에러를 해석하자면 원래는 response로 객체를 받기로 했었는데 String으로 받았다는 얘기이다.
이런 경우에는 response가 DTO랑 다를 경우에 아래와 같은 에러를 받아볼수 있었다.

하지만 나는 DTO 변환기를 통해서 작성했기때문에 response랑 다른부분은 없었고 육안으로 확인해보았을때도 없었다.

해당 오류때문에 한 이틀을 고생했다.
왜냐하면 내가 api를 통해 어떤 response를 받았는지 몰랐기 때문이다.

그래서 나는 http 통신의 로그를 확인해볼수 있도록 okhttp를 사용했다.

그래서 okhttp를 사용한 RetroService class의 소스는 아래와 같다.

package wook.co.weather.models.retrofit;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.concurrent.TimeUnit;

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

public class RetrofitService {
    public Retrofit getRetroInstance(String baseUrl){

        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(1, TimeUnit.MINUTES) //연결하는데 타임아웃 설정을 1분으로 설정
                .readTimeout(1,TimeUnit.MINUTES) //읽어오는데 타임아웃 설정을 1분으로 설정
                .addInterceptor(interceptor).build();

        Gson gson = new GsonBuilder()
                .setLenient()
                .create();

        return new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
    }
}



위처럼하고 api를 호출해보니 아래와 같은 결과를 받았다.

D/OkHttp: <-- 200 OK http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey=0gr5uJKYx6b%252BlhmOt%252BDm%252BfbcxVdiG7U407njrJ3YSFLlrckPeysX5fHfT0dwQRHHs4m0z5ELtDx9jcZ%252FZ3qoVA%253D%253D&numOfRows=50&pageNo=1&dataType=JSON&base_date=20210906&base_time=0500&nx=55&ny=127 (120ms) Content-Type: text/xml;charset=UTF-8 Content-Length: 229 Date: Mon, 06 Sep 2021 16:07:23 GMT D/OkHttp: Server: NIA API Server <OpenAPI_ServiceResponse> <cmmMsgHeader> <errMsg>SERVICE ERROR</errMsg> <returnAuthMsg>SERVICE_KEY_IS_NOT_REGISTERED_ERROR</returnAuthMsg> <returnReasonCode>30</returnReasonCode> </cmmMsgHeader> </OpenAPI_ServiceResponse> <-- END HTTP (229-byte body) D/MAgencyRepo: onFailure : java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $


그럼 이제 내가 어떤 부분에서 오류가 생겼는지 찾았다.
바로 서비스키가 아직 등록되지 않았다는 뜻이다.

위와 같은 에러가 나는 경우는 보통 api의 서비스키가 아직 서버쪽에 등록되어있지 않았을때이다.

하지만 나같은 경우는 postman으로 정상 호출이 되기때문에 이문제가 아니였다.
그래서 구글링을 통해서 알아낸 사실은 우리가 api를 호출할때 입력한 서비스키를 자바에선 인코딩을 해서 보낸다는것이다.

하지만 나는 이사실을 모르고

아래와 같이 인코딩 되어 있는 키를 사용해서 등록되어 있지 않은 서비스키라고 말하는것이다.

따라서 인코딩 되어 있지 않은 키를 사용해서 api를 호출하니 api가 정상 호출 되었다.

D/OkHttp: <-- 200 OK http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey=0gr5uJKYx6b%2BlhmOt%2BDm%2BfbcxVdiG7U407njrJ3YSFLlrckPeysX5fHfT0dwQRHHs4m0z5ELtDx9jcZ%2FZ3qoVA%3D%3D&numOfRows=50&pageNo=1&dataType=JSON&base_date=20210906&base_time=0500&nx=55&ny=127 (139ms) Content-Language: ko-KR Set-Cookie: JSESSIONID=Inj2DW71dYyT4YBYFNfbPZSNPXGoOfC0BCre2i2cD1BK9NhHpyqJJq1KY1Tadvdx.amV1c19kb21haW4vbmV3c2t5Mw==; Path=/1360000/VilageFcstInfoService_2.0; HttpOnly; Domain=apis.data.go.kr Content-Type: application/json;charset=UTF-8 Date: Mon, 06 Sep 2021 16:11:30 GMT Server: NIA API Server D/OkHttp: {"response":{"header":{"resultCode":"00","resultMsg":"NORMAL_SERVICE"},"body":{"dataType":"JSON","items":{"item":[{"baseDate":"20210906","baseTime":"0500","category":"TMP","fcstDate":"20210906","fcstTime":"0600","fcstValue":"19","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"UUU","fcstDate":"20210906","fcstTime":"0600","fcstValue":"-2.1","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VVV","fcstDate":"20210906","fcstTime":"0600","fcstValue":"-0.8","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VEC","fcstDate":"20210906","fcstTime":"0600","fcstValue":"70","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"WSD","fcstDate":"20210906","fcstTime":"0600","fcstValue":"2.4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SKY","fcstDate":"20210906","fcstTime":"0600","fcstValue":"4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PTY","fcstDate":"20210906","fcstTime":"0600","fcstValue":"0","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"POP","fcstDate":"20210906","fcstTime":"0600","fcstValue":"30","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PCP","fcstDate":"20210906","fcstTime":"0600","fcstValue":"강수없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"REH","fcstDate":"20210906","fcstTime":"0600","fcstValue":"80","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SNO","fcstDate":"20210906","fcstTime":"0600","fcstValue":"적설없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"TMP","fcstDate":"20210906","fcstTime":"0700","fcstValue":"20","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"UUU","fcstDate":"20210906","fcstTime":"0700","fcstValue":"-2.2","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VVV","fcstDate":"20210906","fcstTime":"0700","fcstValue":"-0.9","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VEC","fcstDate":"20210906","fcstTime":"0700","fcstValue":"68","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"WSD","fcstDate":"20210906","fcstTime":"0700","fcstValue":"2.5","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SKY","fcstDate":"20210906","fcstTime":"0700","fcstValue":"4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PTY","fcstDate":"20210906","fcstTime":"0700","fcstValue":"0","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"POP","fcstDate":"20210906","fcstTime":"0700","fcstValue":"30","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PCP","fcstDate":"20210906","fcstTime":"0700","fcstValue":"강수없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"REH","fcstDate":"20210906","fcstTime":"0700","fcstValue":"80","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SNO","fcstDate":"20210906","fcstTime":"0700","fcstValue":"적설없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"TMP","fcstDate":"20210906","fcstTime":"0800","fcstValue":"21","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"UUU","fcstDate":"20210906","fcstTime":"0800","fcstValue":"-3","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VVV","fcstDate":"20210906","fcstTime":"0800","fcstValue":"-0.9","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VEC","fcstDate":"20210906","fcstTime":"0800","fcstValue":"73","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"WSD","fcstDate":"20210906","fcstTime":"0800","fcstValue":"3.2","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SKY","fcstDate":"20210906","fcstTime":"0800","fcstValue":"4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PTY","fcstDate":"20210906","fcstTime":"0800","fcstValue":"0","nx":55,"ny":127},{"baseDate":"2 0210906","baseTime":"0500","category":"POP","fcstDate":"20210906","fcstTime":"0800","fcstValue":"30","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PCP","fcstDate":"20210906","fcstTime":"0800","fcstValue":"강수없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"REH","fcstDate":"20210906","fcstTime":"0800","fcstValue":"75","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SNO","fcstDate":"20210906","fcstTime":"0800","fcstValue":"적설없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"TMP","fcstDate":"20210906","fcstTime":"0900","fcstValue":"22","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"UUU","fcstDate":"20210906","fcstTime":"0900","fcstValue":"-3.9","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VVV","fcstDate":"20210906","fcstTime":"0900","fcstValue":"-0.1","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VEC","fcstDate":"20210906","fcstTime":"0900","fcstValue":"88","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"WSD","fcstDate":"20210906","fcstTime":"0900","fcstValue":"4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SKY","fcstDate":"20210906","fcstTime":"0900","fcstValue":"4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PTY","fcstDate":"20210906","fcstTime":"0900","fcstValue":"0","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"POP","fcstDate":"20210906","fcstTime":"0900","fcstValue":"30","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"PCP","fcstDate":"20210906","fcstTime":"0900","fcstValue":"강수없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"REH","fcstDate":"20210906","fcstTime":"0900","fcstValue":"70","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SNO","fcstDate":"20210906","fcstTime":"0900","fcstValue":"적설없음","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"TMP","fcstDate":"20210906","fcstTime":"1000","fcstValue":"23","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"UUU","fcstDate":"20210906","fcstTime":"1000","fcstValue":"-4","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VVV","fcstDate":"20210906","fcstTime":"1000","fcstValue":"0.3","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"VEC","fcstDate":"20210906","fcstTime":"1000","fcstValue":"95","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"WSD","fcstDate":"20210906","fcstTime":"1000","fcstValue":"4.1","nx":55,"ny":127},{"baseDate":"20210906","baseTime":"0500","category":"SKY","fcstDate":"20210906","fcstTime":"1000","fcstValue":"4","nx":55,"ny":127}]},"pageNo":1,"numOfRows":50,"totalCount":742}}} <-- END HTTP (6900-byte body) I/MAgencyRepo: API CONNECT SUCCESS I/MAgencyRepo: ShortWeather{response=Response{body=Body{totalcount=742, numofrows=50, pageno=1, items=Items{item=[Item{ny=127, nx=55, fcstvalue='19', fcsttime='0600', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.1', fcsttime='0600', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.8', fcsttime='0600', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='70', fcsttime='0600', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.4', fcsttime='0600', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0600', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0600', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0600', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0600', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0600', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0600', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='20', fcsttime='0700', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.2', fcsttime='0700', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0700', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='68', fcsttime='0700', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.5', fcsttime='0700', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0700', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0700', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0700', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0700', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0700', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0700', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='21', fcsttime='0800', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-3', fcsttime='0800', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0800', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='73', fcsttime='0800', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='3.2', fcsttime='0800', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0800', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0800', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0800', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수� I/SplashActivity: ShortWeather{response=Response{body=Body{totalcount=742, numofrows=50, pageno=1, items=Items{item=[Item{ny=127, nx=55, fcstvalue='19', fcsttime='0600', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.1', fcsttime='0600', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.8', fcsttime='0600', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='70', fcsttime='0600', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.4', fcsttime='0600', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0600', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0600', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0600', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0600', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0600', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0600', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='20', fcsttime='0700', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.2', fcsttime='0700', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0700', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='68', fcsttime='0700', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.5', fcsttime='0700', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0700', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0700', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0700', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0700', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0700', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0700', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='21', fcsttime='0800', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-3', fcsttime='0800', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0800', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='73', fcsttime='0800', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='3.2', fcsttime='0800', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0800', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0800', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0800', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강� W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@9068b43 I/MainActivity: ShortWeather{response=Response{body=Body{totalcount=742, numofrows=50, pageno=1, items=Items{item=[Item{ny=127, nx=55, fcstvalue='19', fcsttime='0600', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.1', fcsttime='0600', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.8', fcsttime='0600', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='70', fcsttime='0600', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.4', fcsttime='0600', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0600', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0600', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0600', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0600', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0600', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0600', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='20', fcsttime='0700', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-2.2', fcsttime='0700', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0700', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='68', fcsttime='0700', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='2.5', fcsttime='0700', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0700', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0700', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0700', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수없음', fcsttime='0700', fcstdate='20210906', category='PCP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='80', fcsttime='0700', fcstdate='20210906', category='REH', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='적설없음', fcsttime='0700', fcstdate='20210906', category='SNO', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='21', fcsttime='0800', fcstdate='20210906', category='TMP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-3', fcsttime='0800', fcstdate='20210906', category='UUU', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='-0.9', fcsttime='0800', fcstdate='20210906', category='VVV', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='73', fcsttime='0800', fcstdate='20210906', category='VEC', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='3.2', fcsttime='0800', fcstdate='20210906', category='WSD', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='4', fcsttime='0800', fcstdate='20210906', category='SKY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='0', fcsttime='0800', fcstdate='20210906', category='PTY', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='30', fcsttime='0800', fcstdate='20210906', category='POP', basetime='0500', basedate='20210906'}, Item{ny=127, nx=55, fcstvalue='강수 D/EGL_emulation: eglMakeCurrent: 0xda91a360: ver 2 0 (tinfo 0xda90f1c0)



이번 api 호출을 해보면서 느낀점은 http통신 로그를 꼭 확인해야한다는점을 깨달았다.

반응형