231219 (Android)
소셜 로그인 기능 사용하기(네이버)
1. 네이버 로그인 API를 신청한다.
2. 개발환경설정
- 네이버 로그인 API를 안드로이드 프로젝트 디펜던시에 그래들로 추가한다.
3. 네이버 아이디 로그인 객체를 초기화한다.
- 자바에서는 INSTANCE과정이 추가된다.
NaverIdLoginSDK.INSTANCE.initialize(this,"클라이언트 Id","클라이언트 pw","클라이언트 이름");
4. 네이버 로그인 객체를 xml에 생성해준다.
<com.navercorp.nid.oauth.view.NidOAuthLoginButton
android:id="@+id/buttonOAuthLoginImg"
android:layout_width="wrap_content"
android:layout_height="50dp" />
5. 자바 파일에서 로그인 콜백 처리를 만들어준다.
binding.buttonOAuthLoginImg.setOAuthLogin(new OAuthLoginCallback() {
@Override
public void onSuccess() { }
@Override
public void onFailure(int i, @NonNull String s) { }
@Override
public void onError(int i, @NonNull String s) { } });
6. 프로필 정보를 가져올 수 있다.
new NidOAuthLogin().callProfileApi(new NidProfileCallback<NidProfileResponse>() {
@Override
public void onSuccess(NidProfileResponse nidProfileResponse) {
Log.d("네이버", "onSuccess: "+nidProfileResponse.getProfile().getEmail());
Log.d("네이버", "onSuccess: "+nidProfileResponse.getProfile().getName());
Log.d("네이버", "onSuccess: "+nidProfileResponse.getProfile().getProfileImage()); }
@Override
public void onFailure(int i, @NonNull String s) {
}
@Override
public void onError(int i, @NonNull String s) {
}
});
소셜 로그인 기능 사용하기(카카오)
1. 카카오 디벨로퍼에서 애플리케이션 추가
- 네이티브 앱 키를 사용하여 동작한다.
2. 플랫폼을 등록한다.
- 키 해시가 필요하다
- Signature 는 Android.Content를 임포트 한다.
private void getHashKey(){
PackageInfo packageInfo = null;
try {
packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (packageInfo == null)
Log.e("KeyHash", "KeyHash:null");
for (Signature signature : packageInfo.signatures) {
try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
Log.d("KeyHash", Base64.encodeToString(md.digest(), Base64.DEFAULT));
} catch (NoSuchAlgorithmException e) {
Log.e("KeyHash", "Unable to get MessageDigest. signature=" + signature, e);
}
}
}
3. 플랫폼을 활성화 상태로 설정해준다.
4. 카카오톡 메이븐 레퍼지토리를 setting.gradle의 repository에 추가한다.
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { google() mavenCentral()
maven { url '<https://devrepo.kakao.com/nexus/content/groups/public/>'}
}
5. 그래들로 로그인 api 디펜던시를 추가한다.
//카카오 로그인 API
implementation "com.kakao.sdk:v2-user:2.18.0"
6. 카카오 로그인 객체를 생성하여준다.
- NATIVE_APP_KEY : 카카오 디벨로퍼에 등록한 프로젝트의 네이티브앱 키를 넣어준다.
KakaoSdk.init(this, "{NATIVE_APP_KEY}")
7. 안드로이드 매니페스트 파일에 리다이렉트 url를 추가한다
<activity android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Redirect URI: "kakao${NATIVE_APP_KEY}://oauth" -->
<data android:host="oauth" android:scheme="@string/NATIVE_APP_KEY" />
</intent-filter>
</activity>
8. 로그인 메소드를 작성한다.
Function2<OAuthToken, Throwable, Unit> callback = new Function2<OAuthToken, Throwable, Unit>() {
@Override
public Unit invoke(OAuthToken oAuthToken, Throwable error) {
if(error==null){
Log.d("카카오", "invoke: "+oAuthToken.getAccessToken());
} else {
Log.d("카카오", "invoke: "+error.getMessage());
}
return null;
}
};
//카카오톡 설치여부 확인 true->앱으로 인증(권장)
// false->웹뷰로 인증
if (UserApiClient.getInstance().isKakaoTalkLoginAvailable(this)) {
Log.d("카카오", "kakaoLogin: 카카오톡 설치됨->app");
UserApiClient.getInstance().loginWithKakaoTalk(this, callback);
} else {
Log.d("카카오", "kakaoLogin: 카카오톡 설치 안됨->web");
UserApiClient.getInstance().loginWithKakaoAccount(this, callback);
UserApiClient.getInstance().me((user, throwable) -> {
if(throwable==null){
Log.d("카카오정보", "invoke: "+user.getKakaoAccount().getProfile().getNickname());
Log.d("카카오정보", "invoke: "+user.getKakaoAccount().getProfile().getProfileImageUrl());
} else {
Log.d("카카오정보", "invoke: "+throwable.getMessage());
}
return null;
});
}
searchbar의 리스너 만들기
- 쿼리텍스트리스너를 만들어준다.
- 검색어가 바뀔 때 onQueryTextChange가 실행, 검색버튼 활성화 시 onQueryTextSubmit가 동작하게 된다.
binding.searchBar.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
Log.d("검색", "onQueryTextSubmit: "+query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
Log.d("검색", "onQueryTextChange: "+newText);
return true;
}
});
어댑터에서 동작한 후 부모 프래그먼트, 액티비티에서 반응하도록 하는 방법
- 고정된 부모 요소가 있는 경우 부모 요소를 생성자를 통해 가져온다.
- 고정되지 않은 부모 요소가 있는 경우 콜백을 이용하여 동작하도록 한다.
커스텀 다이얼로그 만들기
- 클래스를 만들고 다이얼로그를 상속받는다.
- 다이얼로그에 넣을 레이아웃 파일을 만들어 setContentView에 넣어준다.
- 내부 처리를 해준다.
public class CustomerDialog extends Dialog {
DialogCustomerUpdateBinding binding;
public CustomerDialog(@NonNull Context context, CustomerVO vo) {
super(context);
binding = DialogCustomerUpdateBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.tvCusId.setText("고객번호:"+vo.getCustomer_id());
binding.edtName.setText(vo.getName());
binding.edtEmail.setText(vo.getName());
binding.btnUpdate.setOnClickListener(v -> {
vo.setName(binding.edtName.getText().toString());
vo.setEmail(binding.edtEmail.getText().toString());
new CommonConn(context, "update.cu")
.addParamMap("customerVO",new Gson().toJson(vo))
.onExcute((isResult, data) -> {
dismiss();//다이얼로그 숨기기
});
});
}
}
라디오 버튼이 있는 다이얼로그 만들기
- AlertDialog.Builder를 이용하여 다이얼로그를 만든다.
- setSingleChoiceItems을 이용하여 라디오버튼 선택지를 만든다.
- 각 선택지에 따른 메소드를 작성하고 다이얼로그를 dismiss하도록 한다.
AlertDialog.Builder builder = new AlertDialog.Builder(FileActivity.this);
builder.setTitle("사진 업로드 방식");
builder.setSingleChoiceItems(new String[]{"갤러리", "카메라"}, -1, (dialog, i) -> {
if(i == 0){
showGallary();
} else if(i == 1){
}
dialog.dismiss();
});
AlertDialog dialog = builder.create();
builder.show();
외부 기능으로 액티비티 실행하는 방법
1. startActivityForResult
- 인텐트를 이용하여 외부 액티비티를 실행하고, startActivityForResult를 이용하여 전환한다.
- 전환할 때 요청코드를 같이 보내고 결과를 받을 때 요청코드에 따른 메소드를 작성하게 된다.
- RequestCode로 요청을 보내고 onActivityResult라는 메소드를 재정의한다.
- onActivityResult에서 요청 코드에 따른 행동을 작성한다.
public void showGallary(){
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_PICK);
startActivityForResult(intent,GALLARY_REQ);
}
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d("갤러리", "onActivityResult: "+requestCode);
if(requestCode == GALLARY_REQ && resultCode == RESULT_OK){
Log.d("갤러리", "onActivityResult: "+ data.getData());
Log.d("갤러리", "onActivityResult: "+ data.getData().getPath());
//불러온 이미지를 이미지뷰에 붙임
Glide.with(this).load(data.getData()).into(binding.imgv);
} else if (requestCode == CAMERA_REQ){
}
}
2. ActivityLauncher
- ActivityResultLauncher<Intent> 를 전역변수로 선언한다.
- onStart에 초기화 해준다.
protected void onStart() {
super.onStart();
launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
});
}
- 카메라실행 후 결과를 받을 Uri를 미리 지정한다.
- 인텐트를 만들어 카메라를 실행시키도록 한다.
- 인텐트에 putExtra로 미리 만들어 둔 카메라 uri를 넣어준다.
- 런처를 이용하여 인텐트를 실행한다.
public void showCamera() {
//카메라로 사용자가 사진을 찍으면 우리가 미리 임시로 만들어둔 URI에 카메라 사진을 외부 저장소에 저장 후 알려줌
cameraUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri);
launcher.launch(cameraIntent);
}
- 카메라로 찍은 파일을 서버에 전송하기
- uri가 있다면 파일 객체를 만들어서 전달한다.
File cameraFile = new File(getRealPath(cameraUri));
//Multipart
RequestBody file = RequestBody.create(MediaType.parse("image/jpeg"), cameraFile);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("andFile", "test.jpg", file);//name:servlet구분자, 실제 파일명, 실제 파일
CommonService service = CommonRetroClient.getRetrofit().create(CommonService.class);
service.clientSendFile("file.f", new HashMap<>(), filePart).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
권한 부여하기
- 중간 권한단계의 허용(YOUTUBE)
- 메니페스트에 명시
- queries에 재명시
- 높은 권한단계의 허용(MEDIA_LOCATION, CAMERA, GPS)
- 메니페스트에 명시
- queries에 재명시
- 메소드를 통해 사용자의 확인을 받음
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 실제로 권한 요청을 받아와야하는것 ↓ -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" /> <!-- 카메라의 경우 안드로이드 하드웨어이기때문에 아래 속성을 추가해줘야한ㄷ. required가 false인경우 미사용도 가능. -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera2"
android:required="true" />
<queries>
<!-- WebView -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<!-- Camera -->
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<!-- Gallery -->
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
<!-- Youtube -->
<intent>
<action android:name="android.media.browse.MediaBrowserService" />
</intent>
</queries>
private void checkPermission(){
//// editor.putInt("permission" , 0);//데이터 0이 들어감.
//// editor.apply();//데이터를 확실히 넣음.
int permission = pref.getInt("permission" , -1);
permission++;
editor.putInt("permission" , permission);
editor.apply();
String[] permissions = { Manifest.permission.CAMERA ,
Manifest.permission.ACCESS_MEDIA_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
} ;//카메라 권한을 String으로 가져옴.
// ContextCompat(액티비티가 아닌곳) , ActivityCompat(액티비티)
for(int i = 0 ; i <permissions.length ; i ++){
//내가 모든 권한이 필요하다면 전체 권한을 하나씩 체크해서 허용 안됨이 있는경우 다시 요청을 하게 만든다.
if(ActivityCompat.checkSelfPermission(this , permissions[i]) == PackageManager.PERMISSION_DENIED) {
if(ActivityCompat.shouldShowRequestPermissionRationale(this , permissions[i])){
//최초 앱이 설치되고 실행 시 false가 나옴.=>사용자가 거부 후 true 재거부=>false
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("권한 요청").setMessage("권한이 반드시 필요합니다.!!미허용시 앱 사용 불가!");
builder.setPositiveButton("확인(권한허용)" , (dialog, which) -> {
//2.권한 설명 후 다시보여줌.
ActivityCompat.requestPermissions(this, permissions, REQ_PERMISSION_DENY);
});
builder.setNegativeButton("종료(권한허용불가)" , (dialog, which) -> {
finish();
});
builder.create().show();//<==넣어줘야함.
}else{
//1.
ActivityCompat.requestPermissions(this, permissions, REQ_PERMISSION);
}
break;
}
}
// int result = ActivityCompat.checkSelfPermission(this , permissions[0]);
// Log.d("권한", "checkPermission: " + result);
// Log.d("권한", "checkPermission: " + PackageManager.PERMISSION_GRANTED);
// Log.d("권한", "checkPermission: " + PackageManager.PERMISSION_DENIED);
//
// if(ActivityCompat.shouldShowRequestPermissionRationale(this , permissions[0])){
// Log.d("권한", "shouldShowRequestPermissionRationale: 설명이 필요한 권한. ");
// ActivityCompat.requestPermissions(this,permissions , REQ_PERMISSION);
// }else{
// Log.d("권한", "shouldShowRequestPermissionRationale: 설명이 x");
// ActivityCompat.requestPermissions(this,permissions , REQ_PERMISSION);
// }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(REQ_PERMISSION == requestCode){
for (int i = 0; i < grantResults.length; i++) {
if(grantResults[i] == PackageManager.PERMISSION_DENIED){
//거절된권한이있음.
checkPermission();
break;
}
}
Log.d("권한", "onRequestPermissionsResult: 권한 요청 완료 ");
}else if(REQ_PERMISSION_DENY == requestCode){
for (int i = 0; i < grantResults.length; i++) {
if(grantResults[i] == PackageManager.PERMISSION_DENIED){
Log.d("권한", "onRequestPermissionsResult: 다시 권한요청 화면을 띄울수가 없음.2회 거절당함. ");
editor.putInt("permission" , -2);
//3.
viewSetting();
//checkPermission();
}
}
}
}
권한 부여 메소드의 구조
- 권한 재확인이 필요한 권한을 배열로 가져온다.
String[] permissions = { Manifest.permission.CAMERA ,
Manifest.permission.ACCESS_MEDIA_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
} ;//카메라 권한을 String으로 가져옴.
- 설정을 열어줘서 권한을 직접 부여받도록 한다.
public void viewSetting(){
Intent intent = new Intent(Settings.ACTION_APPLICATION_SETTINGS);
intent.setData(Uri.parse("package:"+getApplicationContext().getPackageName()));
startActivity(intent);
}
ContentResolver를 이용하여 Uri를 실제 이미지 경로로 바꾸기
//contentReslver라는 컴포넌트를 이용하여 Uri를 통해 실제 이미지의 경로를 조회한다.
//Android 내부에 있는 모든 요소는 전부 table 형태로 저장되어있다.
public String getRealPath(Uri contentUri){
String res = null;//문자열 변수로 리턴하기 위해 변수 초기화
String[] cols = {MediaStore.Images.Media.DATA};//컬럼이름을 받아온다.(조회시 alias)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {//안드 API26이전은 확인 불필요. 그냥 경로를 준다.
Cursor cursor = getContentResolver().query(contentUri, cols, null, null);
if(cursor.moveToFirst()){
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
res = cursor.getString(column_index);
}
}
return res;
}
MultiPart
- 파일이나 이미지와 같은 바이너리 데이터를 서버에 전송할 때 사용하는 기능
- 여러 종류의 데이터를 하나의 요청에 여러 부분으로 나눠서 전송처리를 한다.
- 이메일을 보낼 때 텍스트 영역과 별도로 파일 첨부가 있는 형태와 유사하다
- get요청이 불가능하다 url에 데이터 제한이 있음. post요청 필수
- http, https 요청 : 텍스트 데이터를 주고받는데 사용한다.(파일은 이진데이터를 포함해야 한다.)
멀티파트를 이용하여 서버로 파일 전송하기
//Multipart
RequestBody file = RequestBody.create(MediaType.parse("image/jpeg"), new File(filePath));
MultipartBody.Part filePart = MultipartBody.Part.createFormData("andFile", "test.jpg", file);//name:servlet구분자, 실제 파일명, 실제 파일
CommonService service = CommonRetroClient.getRetrofit().create(CommonService.class);
service.clientSendFile("file.f", new HashMap<>(), filePart).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
}
@Override
public void onFailure(Call<String> call, Throwable t) {
}
});
서블릿에서의 멀티파트 처리
- @MultipartConfig 어노테이션이 있어야 파일 처리가 가능하다.
- get요청은 불가능하기 때문에 doPost를 사용하여야 한다.
- 파일 저장은 복붙하는 것이 좋다.
@WebServlet("/file.f")
@MultipartConfig //<- MultiPart 처리는 CommonFileUpload 등 servlet 3.0 이상은 MultipartConfig만 있어도 됨
public class FileController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Part part = req.getPart("andFile");
System.out.println(part.getName()+" : "+part.getSize()+" : "+part.getContentType());
InputStream fileContent = part.getInputStream();
String uploadpath = "D:\\\\mid\\\\"+part.getSubmittedFileName();//저장 경로
try(OutputStream out = new FileOutputStream(new File(uploadpath))){
int read;
final byte[] fileBytes = new byte[1024];
while((read = fileContent.read(fileBytes)) != -1) {
out.write(fileBytes, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
네이버 맵
1. 네이버 클라우드 플랫폼에 가입한다.
2. 애플리케이션 등록을 하고 클라이언트 아이디, 비밀키를 받아준다.
3. setting에 레퍼지토리 추가를 해준다.
allprojects {
repositories {
google()
mavenCentral()
maven {
url '<https://naver.jfrog.io/artifactory/maven/>'
}
}
}
4. 그래들 빌드에 디펜던시를 추가한다.
dependencies {
// 네이버 지도 SDK
implementation 'com.naver.maps:map-sdk:3.17.0'
}
5. 메니페스트의 애플리케이션 태그에 메타데이터를 추가한다.
<meta-data
android:name="com.naver.maps.map.CLIENT_ID"
android:value="YOUR_CLIENT_ID_HERE" />
6. 맵 api를 사용할 곳에서 지도 객체를 호출한다.
NaverMapSdk.getInstance(this).setClient(
new NaverMapSdk.NaverCloudPlatformClient("YOUR_CLIENT_ID_HERE"));
7. 지도 뷰를 이용하여 지도를 보여줄 수 있다.
<com.naver.maps.map.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
binding.mapView.getMapAsync(this);
8. 맵 관련 설정을 변경하기 위해서 인터페이스를 상속받는다.
implements OnMapReadyCallback
9. 네이버 맵 사용 가이드를 보고 옵션을 변경해줄 수 있다.
@UiThread
@Override
public void onMapReady(@NonNull NaverMap naverMap) {
naverMap.setMapType(NaverMap.MapType.Hybrid); 맵 타입 변경
naverMap.setLayerGroupEnabled(NaverMap.LAYER_GROUP_BUILDING, false);
naverMap.setLayerGroupEnabled(NaverMap.LAYER_GROUP_TRANSIT, true);
naverMap.setIndoorEnabled(true);
}
10. 초기 위치를 변경하거나 검색 시 임의의 위치로 이동하기 위해서 카메라 이동이 필요하다.
CameraUpdate cameraUpdate = CameraUpdate.scrollTo(new LatLng(34.6024675, 126.5446438))
.animate(CameraAnimation.Easing, 1000)
.finishCallback(() -> {
Toast.makeText(getContext(), "카메라 이동 완료", Toast.LENGTH_SHORT).show();
}).cancelCallback(() -> {
Toast.makeText(getContext(), "카메라 이동 취소", Toast.LENGTH_SHORT).show();
});
new Handler().postDelayed(() -> {
naverMap.moveCamera(cameraUpdate);
}, 3000);
11. 사용자 인터페이스도 수정할 수 있다.