104일차(1)/Android App(69) : 모바일 갤러리 기능 구현(3)
- 클릭시 동작할 리스너를 등록했다.
- 람다식으로 작성한 것. 리스너에 override할 메소드가 하나인 경우에만 사용할 수 있다!
- 람다식으로 쓰면 (parent, view, position, id) 형태로 인자의 타입을 생략하여 쓸 수 있다.
- 원래대로 쓰면 이런 구조가 된다.
- 익명의 이너클래스를 이용해 인터페이스를 구현한 것
- parent, view, position, id 4개의 인자가 전달되는 메소드가 오직 한개일 때에만! 사용할 수 있다.
- 리스트의 특정 아이템을 클릭하면 이 메소드 안으로 실행 순서가 들어온다.
- putExtra로 dto를 담아서 intent와 함께 전달한다.
- Serializable은 이렇게 비어 있는 인터페이스이다. 오버라이드할 메소드도 없다. 오로지 타입을 위한 인터페이스!
- 객체를 직렬화할때 사용한다.
- 찾아보면 HashMap, ArrayList, Random 등에도 구현되어 있다.
- java의 대부분의 클래스에 구현되어 있다.
- 즉 intent에 다양한, 대부분의 타입은 다 담을 수 있다는 것!
- Detail Activity에서 이 Intent를 받아와서 사용한다.
activity_detail.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
tools:context=".DetailActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="300dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/writer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="작성자:admin"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="어쩌구 저쩌구..."
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/writer" />
<TextView
android:id="@+id/regdate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="2023.03.07 13:00"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/caption" />
<Button
android:id="@+id/deleteBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="245dp"
android:text="삭제"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/regdate"
android:visibility="invisible"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 이런 형태로 디자인을 대략 잡아준다.
DetailActivity
package com.example.step25imagecapture;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.example.step25imagecapture.databinding.ActivityDetailBinding;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
public class DetailActivity extends AppCompatActivity {
ActivityDetailBinding binding;
SharedPreferences pref;
String sessionId;
GalleryDto dto;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
binding=ActivityDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//DetailActivity가 활성화될 때 전달받은 Intent 객체의 참조값 얻어오기
//GalleryListActivity 에서 생성한 Intent 객체이기 때문에 "dto" 라는 키값으로 GalleryDto 객체가 들어있다.
Intent intent=getIntent();
dto=(GalleryDto)intent.getSerializableExtra("dto");
//이미지 출력(Glide 활용)
Glide.with(this)
.load(dto.getImagePath())
.centerCrop()
.placeholder(R.drawable.ic_launcher_background)
.into(binding.imageView);
//세부 정보 출력
binding.writer.setText("writer:"+dto.getWriter());
binding.caption.setText(dto.getCaption());
binding.regdate.setText(dto.getRegdate());
pref= PreferenceManager.getDefaultSharedPreferences(this);
sessionId=pref.getString("sessionId", "");
//삭제 버튼에 리스너 등록
binding.deleteBtn.setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setMessage("삭제 하시겠습니까?")
.setPositiveButton("네", (dialog, which) -> {
//삭제할 갤러리 사진의 Primary key를 이용해서 삭제 작업을 진행한다.
int num=dto.getNum();
})
.setNegativeButton("아니요", null)
.create()
.show();
});
}
@Override
protected void onStart() {
super.onStart();
new LoginCheckTask().execute(AppConstants.BASE_URL+"/music/logincheck");
}
//로그인 여부를 체크하는 작업을 할 비동기 task
class LoginCheckTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... strings) {
//로그인 체크 url
String requestUrl=strings[0];
//서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
StringBuilder builder=new StringBuilder();
HttpURLConnection conn=null;
InputStreamReader isr=null;
BufferedReader br=null;
boolean isLogin=false;
try{
//URL 객체 생성
URL url=new URL(requestUrl);
//HttpURLConnection 객체의 참조값 얻어오기
conn=(HttpURLConnection)url.openConnection();
if(conn!=null){//연결이 되었다면
conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
conn.setRequestMethod("GET");//Default 설정
conn.setUseCaches(false);//케쉬 사용 여부
//App 에 저장된 session id 가 있다면 요청할때 쿠키로 같이 보내기
if(!sessionId.equals("")) {
// JSESSIONID=xxx 형식의 문자열을 쿠키로 보내기
conn.setRequestProperty("Cookie", sessionId);
}
//응답 코드를 읽어온다.
int responseCode=conn.getResponseCode();
if(responseCode==200){//정상 응답이라면...
//서버가 출력하는 문자열을 읽어오기 위한 객체
isr=new InputStreamReader(conn.getInputStream());
br=new BufferedReader(isr);
//반복문 돌면서 읽어오기
while(true){
//한줄씩 읽어들인다.
String line=br.readLine();
//더이상 읽어올 문자열이 없으면 반복문 탈출
if(line==null)break;
//읽어온 문자열 누적 시키기
builder.append(line);
}
}
}
//서버가 응답한 쿠키 목록을 읽어온다.
List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
//만일 쿠키가 존대 한다면
if(cookList != null){
//반복문 돌면서
for(String tmp : cookList){
//session id 가 들어 있는 쿠키를 찾아내서
if(tmp.contains("JSESSIONID")){
//session id 만 추출해서
String sessionId=tmp.split(";")[0];
//SharedPreferences 을 편집할수 있는 객체를 활용해서
SharedPreferences.Editor editor=pref.edit();
//sessionId 라는 키값으로 session id 값을 저장한다.
editor.putString("sessionId", sessionId);
editor.apply();//apply() 는 비동기로 저장하기 때문에 실행의 흐름이 잡혀 있지 않다(지연이 없음)
//필드에도 담아둔다.
DetailActivity.this.sessionId=sessionId;
}
}
}
}catch(Exception e){//예외가 발생하면
Log.e("LoginCheckTask", e.getMessage());
}finally {
try{
if(isr!=null)isr.close();
if(br!=null)br.close();
if(conn!=null)conn.disconnect();
}catch(Exception e){}
}
//서버에서 응답받은 문자열을 리턴한다.
return builder.toString();
}
@Override
protected void onPostExecute(String jsonStr) {
super.onPostExecute(jsonStr);
try {
//json문자열을 이용해서 JSONObject 객체를 생성한다.
JSONObject obj=new JSONObject(jsonStr);
//로그인 여부
boolean isLogin=obj.getBoolean("isLogin");
if(isLogin){
//로그인된 아이디를 읽어와서
String id=obj.getString("id");
//갤러리 writer와 비교해서 같으면 삭제 버튼을 보이게 한다.
if(id.equals(dto.getWriter())){
//삭제 버튼 보이게하기
binding.deleteBtn.setVisibility(View.VISIBLE);
}
}
}catch (JSONException je){
Log.e("onPostExecute", je.getMessage());
}
}
}
}
- Gradle에 뷰 바인딩 설정을 해놓으면 자동으로 이런 이름의 클래스가 구성된다.
- 사용해서 참조값을 얻어오기!
getLayoutInflater()
- 부모가 이 메소드를 가지고 있다. 메소드에서 LayoutInflater 타입을 리턴해준다.
- 화면구성은 binding 객체를 활용해서 한다.
- 저장한 키값으로 intent 에 저장된 내용 가져오기
- 액티비티에서 Glide를 사용한다.
- Glide.load() 에 문자열을 전달하면 알아서 로딩한다.
(단 이것이 동작하려면 AndroidManifest에 인터넷 설정이 있어야 한다!! 주의!!)
- .centerCrop() 가운데 딱 들어맞도록 설정
- .into() 안에는 이미지뷰의 참조값 넣어주기(바인딩으로 참조값을 구한다)
- binding으로 각각의 TextView 안에 값을 넣어준다.
- 클릭시 크게 보이는 Detail Activity를 구현했다.
* 삭제 버튼 만들기
<Button
android:id="@+id/deleteBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="245dp"
android:text="삭제"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/regdate" />
- 이 버튼은 본인의 사진 Detail 페이지에 들어갔을 때에만 출력되도록 하기.
- activity_detail.xml 파일에서 가시성 여부에 대한 설정값을 지정해줄 수 있다.
- gone : 화면에 공간은 차지하지만 안보이는 것
- invisible : 자리를 차지하지 않으면서 안보이는 것
- xml파일에서 옵션을 지정한 후에, 이 버튼을 어떤 조건에서만 보이게 할지 여부를 액티비티에서 코딩으로 조정할 수 있다.
- 보통 이런 값들은 View 클래스 안에 상수값으로 정의되어 있다.
- 이 코드를 조건부로 넣어주면 된다!
- id라는 키값으로 로그인한 아이디를 받아오고, isLogin 으로 로그인상태 관리
- 여러 곳에서 중복되는, 반복적으로 쓰이는 메소드가 있다면, Utility로 만들어버리는 것이 편하다.
- ctrl+c,v 해서 매번 쓰는것보다는 필요할때 import해서 사용할 수 있도록!
- 로그인 체크용으로 따로 유틸 만들기
- MainActivity의 logincheckTask 를 복사해서 가져와주고
- onCreate 안에 pref, sessionId를 담아주는 코드를 추가.
- 복사해온 메소드 Async의 결과 타입을 String으로 바꿔주었다.
로그인체크해서 true이면
아이디를 읽어내서 동작하기
다른 메소드안에서 사용하기위해 갤러리DTo를필드로 만들어주었다.
- onStart() 메소드를 오버라이드해서 LoginCheckTask 써주기
- 내가 올린 게시물에서만 삭제 버튼이 나타난다.
- 삭제버튼에 리스너 등록하기
- 람다식으로 쓸 수 있다.
- 이 안에는 사진 정보를 서버 DB에서 삭제하는 작업이 들어가야 하는데 코드가 길고 복잡하다...
- 유틸리티로 만들어볼 예정
- java 하위패키지 생성- 새 클래스 생성
MyHttpUtil (미완성)
package com.example.step25imagecapture.util;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import androidx.annotation.Nullable;
import java.util.Map;
/*
- Http 요청을 할때 서버가 응답하는 쿠키를 모두 읽어서 저장하고 싶다.
- 다음번 Http 요청을 할때 저장된 쿠키를 모두 보내고 싶다.
- 쿠키 값이 수정되어서 응답되면 저장되어 있는 쿠키를 수정해야 한다.
- 그러면 쿠키를 SQLiteDataBase를 활용해서 관리하면 빠르게 처리할 수 있지 않을까?
*/
public class MyHttpUtil {
//필드
private Context context;
private DBHelper dbHelper;
//생성자
public MyHttpUtil(Context context){
this.context=context;
//DBHelper 객체의 참조값을 얻어내서 필드에 저장해 둔다.
dbHelper=new DBHelper(context, "CookieDB.sqlite", null, 1);
}
//이 유틸리티를 사용하는 곳에서 구현해야 하는 인터페이스
public interface RequestListener{
public void onSuccess(int requestId, String data);
public void onFail(int requestId, Map<String, Object> result);
}
class DBHelper extends SQLiteOpenHelper{
public DBHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
//해당 DBHelper 를 처음 사용할 때 호출되는 메소드(new DBHelper() 를 처음 호출할 때)
@Override
public void onCreate(SQLiteDatabase db) {
//테이블을 만들면 된다.
String sql="CREATE TABLE board_cookie (cookie_name TEXT PRIMARY KEY, cookie TEXT)";
db.execSQL(sql);
}
//DB를 리셋(업그레이드)할때 호출되는 메소드
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//업그레이드할 내용을 작성하면 된다.
db.execSQL("DROP TABLE IF EXISTS board_cookie"); //만일 테이블이 존재하면 삭제한다.
//다시 만들어질 수 있도록 onCreate() 메소드를 호출한다.
onCreate(db);
}
}
/*
GET 방식 요청을 하는 메소드
*/
public void sendGetRequest(int requestId, String requestUrl, Map<String, String> params,
RequestListener listener){
//GET방식 요청을 할 비동기 Task 객체를 생성해서
GetRequestTask task=new GetRequestTask();
//필요한 값을 넣어주고
task.setRequestId(requestId);
task.setRequestUrl(requestUrl);
task.setListener(listener);
//비동기 Task를 실행한다.
task.execute(params);
}
private class GetRequestTask extends AsyncTask<Map<String, String>, Void, String>{
//필요한 필드 구성
private int requestId;
private String requestUrl;
private RequestListener listener;
public void setRequestId(int requestId) {
this.requestId = requestId;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public void setListener(RequestListener listener) {
this.listener = listener;
}
@Override
protected String doInBackground(Map<String, String>... maps) {
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
}
}
}
- 세션아이디만 쿠키로 보내면 된다.
- 응답한 다른 쿠키는 읽어오지 않는다.
- 쿠키를 SQLiteDataBase를 활용해서 자체 관리할 것!
- 클래스를 만들어주고 상속하면서 생성자, 메소드 2개를 override 한다.
- 이렇게 쿠키이름과 쿠키 문자열을 따로 관리한다. key:value 값으로 관리된다.
- 반복문을 집어넣어서 돌릴 것!
- 필드와 생성자를 만들어주고, DBHelper 객체를 사용한다.
- 버전은 1로 고정
- 사용하는 액티비티에서 new MyHttpUtil(this) 로 작성해주면
생성자의 인자로 받는 Context에 Activity의 참조값이 전달되어서 DBhelper를 사용할 준비가 된다.
getReadableDataBase()
getWritableDataBase()
- 이 메소드를 사용해서 SQLiteDataBase 객체를 리턴한다.
- 비동기 작업이므로 성공했을 때 데이터를 넣어주고 실패했을 때는 실패 정보를 전달하도록!
- 인터페이스 안에 전달
- 이 안에 JSON 문자열이 들어오도록 한다.
- GET 방식 요청을 하는 메소드
- id, url을 받고 파라미터가 있다면 받아주고, 위에서 만든 리스너를 인자로 받아준다.
- 아래 GetRequestTask 에서 한 작업의 결과값을 리스너를 통해서 통보할 것이다.
- 이 안에서 사용할 수 있도록 각각의 필드를 선언
- generate를 사용해서 setter 메소드를 만들어준다.
- setRequestId() 등 3개의 메소드를 사용해서 task 안에 값을 넣어준다.
- 인자를 넣어주어서 sendGetRequest 메소드 완성해주기
- Activity에서 get방식 요청을 하려면 이렇게 하면 된다. 작업을 좀더 간편하게 처리하기 위한 것!
- listener 인자에 this가 들어오도록 하게 하려면 Activity에 리스너를 구현해주어야 한다.
(유틸리티는 아직 미완성. 이어서 만들 예정...)
'국비교육(22-23)' 카테고리의 다른 글
107일차(1)/Android App(71) : 모바일 갤러리 기능 구현(5) (0) | 2023.03.15 |
---|---|
105일차(1)/Android App(70) : 모바일 갤러리 기능 구현(4) (0) | 2023.03.11 |
103일차(1)/Android App(68) : 모바일 갤러리 기능 구현(2) (0) | 2023.03.09 |
102일차(1)/Android App(67) : 모바일 갤러리 기능 구현(1) (0) | 2023.03.08 |
101일차(1)/Android App(66) : 카메라 앱으로 사진 촬영, 저장(3) / 서버 전송 (0) | 2023.03.07 |