이전에는 FusedLocationProvider 사용법의 6단계까지 알아봤다.
1. build.gradle(프로젝트) 파일에 classpath 추가하기.
2. build.gradle(Module:app) 파일에 dependencies 추가하기
3. manifest에 권한 추가하기.
4. Activity에서 위치정보 권한 확인하기.
5. 권한 확인 요청에 관한 콜백 메소드 만들기
6. 구글 플레이 서비스에서 위치정보 사용하는지 안하는지 체크하기 - 이전 포스트-
7. 구글 플레이 서비스 위치정보 사용 콜백 메소드 만들기
8. 위치 정보 업데이트 요청 메소드 만들기
9. 위치정보 업데이트 콜백 메소드 만들기
그럼 이제는 다음 단계인 7단계부터 알아보자.
https://wpioneer.tistory.com/193
7. 구글 플레이 서비스 위치정보 사용 콜백 메소드 만들기
6번 과정에서 이제 구글플레이 서비스 사용에 대한 요청 메시지를 띄우게 했다.
그래서 우리의 선택에 따라 콜백되는 콜백 함수를 만들어야 한다.
소스는 아래와 같다.
@Override
public void onActivityResult(int requstCode,int resultCode,Intent data) {
super.onActivityResult(requstCode, resultCode, data);
if(requstCode == 200) { //요청코드가 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();
}
}
}
나는 6번에서 요청을 할떄 requestCode를 200으로 줬기때문에 requestCode가 200일때 예 아니오를 확인하게 했다.
자세한 내용은 주석으로 설명해놨으니 확인해면 될것 같다.
8. 위치 정보 업데이트 요청 메소드 만들기
그럼 이제 위치정보 업데이트 요청을 해보자.
나는 위치정보 업데이트는 ViewModel에서 하게 했기 때문에 ViewModel에 있는 요청 메소드를 호출하였다.
(ViewModel의 객체 생성은 해당 Activity의 onCreate()에서 했다.)
//MAgencyViewModel 객체 생성
mavm = new ViewModelProvider(this).get(MAgencyViewModel.class);
저렇게 구글플레이 서비스를 이용할수 있다고 되어 있으면 ViewModel의 위치 정보 업데이트 요청 메소드를
호출하는것이다.
그럼 이제 ViewModel의 위치 정보 업데이트 메소드의 소스는 아래와 같다.
@SuppressLint("MissingPermission")//위치권한 체크안해도 된다고 하는 부분 안하는 이유는 SplashActivity에서 이미 했기 때문이다.
public void requestUpdate(LocationRequest locationRequest){
Log.d(TAG,"LocationRequest have been request");
mldGi = new MutableLiveData<GeoInfo>(); //LiveData 객체 생성
requestLocationUpdate = true;
LocationServices.getFusedLocationProviderClient(getApplication())
.requestLocationUpdates(locationRequest,lcb,null); //위치정보 업데이트 요청
}
위 소스에 대한 설명은 주석으로 해놨으니 확인하면 될것 같다.
그럼 이제 위치정보 업데이트 요청에 대한 콜백메소드에 대해서 알아보자.
9. 위치정보 업데이트 콜백 메소드 만들기
위치정보 콜백 메소드는 위치정보에 대한 값을 받아왔을때 호출되는 메소드이다.
근데 일단 콜백메소드를 만들기 전에 우리는 LocationCallBack 변수를 만들어야 한다.
private LocationCallback lcb;
그럼 이제 해당 변수에 대한 객체를 생성하고 콜백 메소드를 만들어야 하는데 나는 객체 생성과 콜백 메소드를
ViewModel의 생성자 안에 넣었다. 생성자 안에 넣은 이유는 LocationCallBack 변수에 대한 객체를 생성해야하는데
그부분은 생성자에서 하는게 제일 낫겠다고 생각했기도 했고 StackOverFlow에서 해당 방법을 추천해줬기 때문이다.
소스는 아래와 같다.
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); //LiveData에 데이터를 입력한다.
}
};
}
ViewModel에서 getApplication()이 가능했던 이유는 ViewModel 클래스에서 AndroidViewModel 를
extends 했기 때문이다.
ViewModel에서 getApplication() 을 못해 고생했지만 일반적인 extends ViewModel 과는 다르게 Application이 있는
AndroidViewModel을 extends해서 쉽게 LocationUpdate를 요청할수 있었다.
무튼 이렇게 콜백 메소드를 하게 되면 이제 위치정보를 받아오게 되면
private MutableLiveData<GeoInfo> mldGi;
LiveData인 mldgi 에 값을 넣어 해당 LiveData에 변경사항이 생긴다.
그럼이제 해당 LiveData를 observe하는 Activity의 onChanage() 가 호출이 된다.
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();
}
});
}
그럼 이제 전체 소스를 보여주겠다.
SplashActivity class
package wook.co.weather.view.splash;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
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.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
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 ShortWeather sw;
private MAgencyViewModel mavm;
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 FusedLocationProviderClient flpc;
@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();
checkLocationPermission();
}
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();
}
}
//권한요청되었을때 콜백되어지는 함수
@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);
}
},1000);
}
});
}
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){ //locationResult를 받지 못했을떄 진입
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); //LiveData에 데이터를 입력한다.
}
};
}
//위치 정보 이용 권한 허가를 받지 못했을떄 호출 하는 부분
public void defaultLocation() {
//GpsTransfer 객체 생성
gpt = new GpsTransfer();
//GpsTransfer 위도와 경도를 원주로 설정
gpt.setxLat(76);
gpt.setyLon(122);
gpt.transfer(gpt, 1);
setGeoInfo(gpt);
callApi(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 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; }
}
참고 사이트
https://manorgass.tistory.com/82
'코딩일기 > 날씨앱 만들기 프로젝트' 카테고리의 다른 글
날씨 앱 만들기 : Git에서 API 키 숨기기 (feat. gitignore) (0) | 2021.09.28 |
---|---|
날씨 앱 만들기 : 네트워크 연결 체크하기 (0) | 2021.09.28 |
날씨 앱 만들기 : FusedLocationProvider 사용법 Part 1 (MVVM / Java) (0) | 2021.09.25 |
날씨 앱 만들기 : 기상청 API 분석 및 현재 시간 날씨 정보 받기 (0) | 2021.09.11 |
날씨 앱 만들기 : 위도와 경도 기상청 격자 정보 X,Y 로 변환 (Java) (0) | 2021.09.09 |