프롤로그 참고 - [prologue] 2019 부스트코스 에이스 합격! (안드로이드)

(요약 - 부스트코스 강의를의 단순 요약하는 것보다는, 실질적으로 프로젝트에 도움이 될 핵심포인트들과 꿀팁들 등을 위주로 작성하기로 하였다.)

 

 


< 게 시 글    목 차 >

프로젝트3에서 구현해야할 화면!!

 

 

- 프로젝트 3의 핵심 포인트 3가지 ! 

  1. 이제는 액티비티가 여러 개다! startActivity(), startActivityForResult()를 사용하여 화면을 전환시켜보자.
  2. 피호출된 액티비티가 종료하면서 결과를 리턴하는 방법에 대하여 알아보자.(setResult(), finish())
  3. 서비스와 브로드캐스트 수신자.(link)

+ additional tips 

  1. '모두보기' 화면에는 MainActivity로 돌아갈 수 있는 별도의 버튼이 없다. 액션바에 뒤로가기 버튼을 만들어보자.
  2. Intent 객체에 다양한 data담기 - 기본자료형 이외의도, 직접만든 클래스 객체ArrayList<클래스> 등도 담아보자!! (★★★)
  3. RatingBar의 값을 사용자가 직접 세팅할 수 있게하자(작성하기 화면)
  4. '작성하기' Activity에서 작성한 한줄평이 '모두보기' 화면의 한줄평 리스트에 추가되어 보일 수 있도록 해보자. (필수x)

*** 게시글이 길기 때문에 ctrl+f 로 원하는 부분을 찾아서 보자!

 


프로젝트 3 핵심 포인트 3가지 !

 

1. 이제는 액티비티가 여러개다!

startActivity(), startActivityForResult()를 사용하여 화면을 전환시켜보자.

 

PJT2 까지는 MainActivity 한 개의 액티비티 내에서 모든 활동이 이루어졌다.

이제는 '작성하기', '모두보기' 버튼을 클릭하면 그에 맞는 Activity가 실행되도록 만들어야한다!

 

새로운 액티비티를 호출하는 것의 기본틀은 다음과 같다.

Intent intent = new Intent(getApplicationContext(), 호출하고픈_액티비티_이름.class);
startActivity(intent);

 '작성하기' 버튼에 onClickListener 내에 위와 같은 코드를 작성한 뒤, 버튼을 클릭하면 해당 클래스의 화면이 뜨게된다.

 

 

Intent intent = new Intent(getApplicationContext(), WriteReviewActivity.class);

// intent 객체 안에 data를 담자.
float rating_value = ratingbar.getRating();
intent.putExtra("rating", rating_value); // "rating"이라는 이름으로 intent에 float값이 저장되었다.

startActivityForResult(intent, REQUEST_WRITE_BUTTON_CLICKED);

호출하는 액티비티에 정보를 전달해주어야 하는경우, putExtra() 메소드를 활용하여 값을 intent에 싣는다.

 

 

액티비티를 호출한 뒤, 해당 액티비티에서 결과값을 전달받고 싶은 경우, startActivityForResult()를 사용한다.

위 메소드를 사용하여 액티비티를 호출하면, 호출된 액티비티가 종료하면서 자신을 호출한 액티비티에게 결과값을 리턴한다. 사용예시는 다음과 같다.

 

Intent intent = new Intent(getApplicationContext(), ReviewActivity.class);

// 파라미터: startActivityForResult(Intent intent, int requestCode);
startActivityForResult(intent, 1004); // 임의의 숫자.

두번째 파라미터에 임의의 int값을 설정한다.

 

현재 새로운 액티비티를 호출하는 주체가 MainActivity라고 해보자. MainActivity에서는 위의 코드에서 처럼 ReviewActivity만을 호출하는 것이 아니라, 여러개의 다른 액티비티를 호출할 수 있다. 따라서 MainActivity가 startActivityForResult()를 사용하여 다양한 액티비티를 호출하고, 이 액티비티들이 종료하면서 MainActivity에게 결과값을 리턴할 것인데, MainActivity는 어떤 액티비티가 자신에게 결과값을 전달한 것인지 두번째 파라미터인 requestCode를 통하여 구분한다.

 

결과값을 받아서 처리하는 코드는, 

protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{
	// codes..
}

에 작성한다.

 

첫번재 파라미터 requestCode가 startActivityForResult에서 사용한 두번째 파라미터 값에 해당한다.(1004)

두번째 파라미터는 피호출 액티비티에서 실행을 종료하면서 설정하는 값으로, 곧 알아보게 된다.(RESULT_OK, RESULT_CANCELED 등)

세번째는 Intent 객체로, 피호출 액티비티의 각종 결과값이 저장되어 있을 것이다. 원하는 정보를 뽑아서 사용하면 된다.

 

 

...글로 설명하니 어려워보인다. 사용 예시를 보면 금방 이해가 될 것이다!

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);

        // 전달받은 requestCode에 따라 상황에 맞는 Toast메세지를 생성한다.
        switch (requestCode)
        {
        	// startActivityForResult(intent, 1004); 를 통해 실행된 액티비티에서 값을 리턴받으면, 여기서(case 1004) 처리된다!
            case 1004:
                // 세번째 파라미터로 전달된 intent 객체에서 원하는 값을 뽑아서 사용한다.
                // ex)
                float rating = intent.getFloatExtra("rating", 0.0f);
                break;

            case 1005:
            	// 다른 액티비티에서 결과값을 받은경우...
                break;
                
            ...
        }
    }

1004번으로 호출한 액티비티가 값을 리턴한 경우, 결과값을 처리할 코드를 case 1004: 부분에 작성한다.

intent.getFloatExtra(), intent.getStringExtra() 등 다양한 메소드를 통하여 원하는 값을 뽑아낸다.

(예시로 사용한 getFloatExtra()의 첫번째 파라미터는 데이터의 이름, 두번째는 해당값이 null일 경우 default로 사용하게 될 값이다.)

 

피호출된 액티비티가 결과값을 전달하는 방법은 이제 다음 파트에서 바로 소개한다!

 

 

 

 

2. 피호출된 액티비티가 종료하면서 결과를 리턴하는 방법에 대하여 알아보자.(setResult(), finish())

 

파트1에서 액티비티를 호출하고, 결과를 리턴받아서 활용하는 법에 대하여 알아보았다.

이제 호출된 액티비티에서 작성해야 하는 코드를 살펴본다아

 

피호출..이라는 단어가 거슬린다. 앞으로 그냥 위에서 사용한 예시와 같이,

MainActivity(호출)와 ReviewActivity(피호출)라는 이름을 사용하여 설명하겠다

(MainActivity가 startActivityForResult()를 사용하여 ReviewActivity를 호출하였다.)

 

ReviewActivity 내에서 다양한 정보를 입력하고, '저장' 버튼을 클릭하여 ReviewAcitivy를 종료하고 다시 MainActivity로 돌아가려 한다.

'저장' 버튼에 대한 onClickListener() 내에서 전달할 결과값을 설정하자.

 

백문이불여일견.

저장버튼.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent intent = new Intent();
        
        // data 삽입
        intent.putExtra("data이름", data_값);
        intent.putExtra()
        ...
        // resultCode 설정
        // 우선은 RESULT_OK, RESULT_CANCELED 정도만 알아두자.
        setResult(RESULT_OK, intent);
        
        // 현재 액티비티 종료. MainActivity의 onActivityResult() 가 호출되고, 위의 intent 객체가 함께 전달된다.
        finish();
    }
});
취소버튼.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        setResult(RESULT_CANCELED);
        finish();
    }
});

저장버튼을 클릭하면, intent에 다양한 data를 저장하고, 필요시 resultCode를 설정한 뒤 finish()를 호출하여 액티비티를 종료한다.

 

파트1에서 resultCode에 관한 부분은 설명하지 않았다.

하지만 간단하다. 아래와 같이 사용하면 된다.

case 1004:
    if(resultCode == RESULT_OK){
		// 저장버튼을 통해 종료된 경우
    }
    else if(resultCode == RESULT_CANCELED){
		// 취소버튼을 통해 종료된 경우
    }
    break;

 

이렇게 파트1,2 를 통하여 새로운 액티비티를 호출하는 방법에 대하여 알아보았다.

requestCode는 가독성을 위하여 예시로 1004라는 값을 사용하였지만,

클래스에 전역으로 아래와 같이 선언한 후, 변수명을 통해 사용해야 가독성도 높아지고 코드 관리도 쉬워진다.

private static final int RESULT_CODE_BUTTON_CLICKED = 1004;
switch(requestCode)
{
	case REQUEST_CODE_BUTTON_CLICKED:
    	// ...

{

 

 

 

 

3. 서비스와 브로드캐스트 수신자.

 

 

prologue에서 말한것과 같이, 부스트코스 강의 내용을 단순 요약하기보다는 실제로 프로젝트를 수행할 때

필수로 알아야 할 개념 간략정리 + 프로젝트 수행에 도움이 되는 꿀팁 을 위주로 작성하기로 하였다.

 

'서비스' 와 '브로드캐스트 수신자'와 관련된 부분은 챕터3 강의에 소개는 되지만,

프로젝트 수행에는 몰라도 진행이 가능하다.

 

따라서 스킵하려했으나, 기존에 관련된 내용을 학습하면서 정리해놓은 글이 있기에

링크만 남기고 추가 부연설명은 여기서 하지 않겠다.

 

https://chebaum.tistory.com/25

 

[안드로이드] Service 활용하기(백그라운드 실행)

...더보기 간단설명... 다음의 (1), (2) 코드를 모두 작성하게 되면, 액티비티 내의 특정 버튼을 누름으로써 서비스를 실행시키고, 이렇게 실행된 서비스는 5초간 sleep() 했다가 액티비티를 호출하여 액티비티에..

chebaum.tistory.com

https://chebaum.tistory.com/27

 

[안드로이드] 브로드캐스트 리시버(Broadcast receiver) - 문자(SMS)메세지 이벤트처리

* 우선 Broadcast Receiver 만들기! [ 프로젝트이름 왼쪽클릭 -> New -> Other -> Broadcast Receiver 선택 -> 원하는 이름으로 설정] 의 과정을 거치면, AndroidManifest.xml에 자동으로 ..

chebaum.tistory.com

 

 

 

 

 

 


+ additional tips 

 

1. '모두보기' 화면에는 MainActivity로 돌아갈 수 있는 별도의 버튼이 없다.

액션바에 뒤로가기 버튼을 만들어보자.

 

'작성하기' 화면에서는 '취소' 버튼이 있어서, 해당 버튼을 클릭해서 MainActivity로 돌아갈 수 있다.

하지만 '모두보기' 화면에는 별도의 버튼이 없고, 대신 액션바에 뒤로가기 버튼을 생성하여 사용한다.

 

이에 대하여 강의에서 가르쳐주지 않기에 스스로 구글링해서 알아내야 한다.

 

매우 간단하다. 코드를 보고 그대로 따라하면 된다.

 

@Override
protected void onCreate(Bundle savedInstanceState){

	...
    
    // 툴바에 뒤로가기버튼 생성코드
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    
    ...
    
}

너무 간단해서 민망하다. onCreate() 내부에 위의 코드 한줄만 추가하면 된다.

 

이제 이 버튼 선택에 대한 이벤트 처리를 해주어야한다.

ctrl+O 를 누르고 onOptionsItemSelected() 메소드를 오버라이딩 해주자.

 

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId())
    {
        case android.R.id.home: // 툴바에 있는 뒤로가기 화살표 버튼
			...
            finish();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

case android.R.id.home:

     // 요기에 필요한 코드를 작성해주면 된다. 끗!

 

 

 

 

2. Intent객체에 다양한 data 담기

(기본자료형 Int, Float.. 외에도 직접만든 클래스 객체 및 ArrayList<> 까지 담아보자!!) (★★★)

2-1: 기본자료형

2-2: 직접만든 클래스

2-3: ArrayList<직접_만든_클래스>

 


2-1) 기본자료형

 

우선 기본자료형을 intent 객체에 넣는 것은 다양한 내장 method를 사용하면 된다.

/** intent에 data 담기 **/
int num = 1;
int[] intArr = new int[] { 1, 2, 3, 4, 5 };
String[] strArr = new String[] { "aaa", "bbb", "ccc" };

intent.putExtra("data1", num);
intent.putExtra("data2", intArr);
intent.putExtra("data3", strArr);

/** data 뽑아내기 **/
int num = intent.getIntExtra("data1");
int[] intArr = intent.getIntArrayExtra("data2");
String[] strArr = intent.getStringArrayExtra("data3");

이렇게나 많다....!

 


2-2) 직접만든 클래스

 

하지만, 여기서 제공하지 않는 타입의 클래스 객체를 담을 때는 어떻게 해야할까...?

굉장히 뭔가 어려울 것만 같다.(나는 그랬다)

하지만 알고보면 굉장히 쉽다!

 

우선 Review 라는 이름의 클래스를 담아서 보낸다고 하자.

해야할 것은 두가지.

 

   (1) 클래스의 모든 attribute에 대하여 setter와 getter 함수를 만들어준다.

   (2) Serializable 클래스를 implement 한다. 

public class Review implements Serializable  { // <- (2) Serializable 상속받기

	int number;
    ...
    
    public Review(){ ... }
    
    (1) getter, setter 세팅해주기
    public void setNumber(int number) { this.number = number; }
    public int getNumber() { return number; }
    
    // + 다른 멤버변수에 대하여도 getter, setter 작성
}

이렇게만 해주면, 이제는 위의 기본자료형 보내듯이 보낼 수 있다!!

// intent 에 담을 때
Review review = new Review();
intent.putExtra("임의의이름", review);

// intent 에서 꺼낼 때
Review review = (Review) intent.getSerializableExtra("임의의이름");

위와 같이 사용하면 된다. 쉬워라..

 

 


2-3) ArrayList<직접_만든_클래스>

(구현방법에 따라 이 부분은 모르고 넘어가도 프로젝트 구현에 지장이 없다!!)

 

위에서 Review 객체를 담는법에 대해 알아보았다.

근데 만약 ArrayList<Review> 를 보내고싶다면...? 또 골치아프다.

이부분은 스스로 구글링으로 알아내는데 좀 시간이 걸렸다.

(거기에 내가 만든 Review 클래스 내에 멤버변수로 BitmapDrawable 객체가 들어가있는데, 이때문에 더욱 어려웠다)

 

우선 보내고자하는 Review 클래스의 예시는 다음과 같다. 

*** 이번 케이스의 경우, Parcelable 클래스를 상속받아야한다!

public class Review implements Parcelable {
    BitmapDrawable userIcon;
    float rating;

    public Review() {
    }

    public Review(BitmapDrawable userIcon, float rating) {
        this.userIcon = userIcon;
        this.rating = rating;
    }

    /**  Parcelabe implement 하면서 추가된 부분 **/
    public Review(Parcel in) {
        // *** BitmapDrawable도 아래와같은 방식으로 넘길 수 있다. ***
        Bitmap bitmap = in.readParcelable(BitmapDrawable.class.getClassLoader());
        this.userIcon = new BitmapDrawable(bitmap);

        this.rating = in.readFloat();
    }

    /**  Parcelabe implement 하면서 추가된 부분 **/
    @Override
    public int describeContents() {
        return 0;
    }

    /** Parcelabe implement 하면서 추가된 부분 **/
    @Override // 주의! 생성자에서의 멤버변수 초기화 순서와 동일해야한다.
    public void writeToParcel(Parcel parcel, int i) {
        Bitmap bitmap = userIcon .getBitmap();
        parcel.writeParcelable(bitmap, i );
        
        parcel.writeFloat(this.rating);
    }

    /** Parcelabe implement 하면서 추가된 부분 **/
    // Parcelable를 implement 하는 경우 CREATOR를 만들어줘야한다.(아래의 코드는 필요할때마다 클래스의 이름만 변경하고 복붙해서 사용하면 된다)
   
    @SuppressWarnings("rawtypes")
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        @Override
        public Object createFromParcel(Parcel parcel) {
            return new Review(parcel);
        }

        @Override
        public Review[] newArray(int size) {
            return new Review[size];
        }
    };

    // ... 이외에도 각 멤버변수에 대하여 getter와 setter를 작성해주어야 한다. 생략
}

복잡해 보일 수 있으나, 헷갈리면 BitmapDrawable 과 관련된 코드는 지우고 보아보자.

단순히 생성자를 하나 더 오버로딩하여 만들고 멤버변수 개수만큼 in.readFloat(), in.readString() 등의 메소드를 호출한다.

또 위의 순서 그대로 writeToParcel에서 다시 parcel.writeFloat(), parcel.writeString() 를 호출하면 끗.(;;)

사실상 우리가 작성해야하는 코드는 거의없고 다 복붙하면 된다.

 

Intent intent = new Intent(getApplicationContext(), A.class);
ArrayList<Review> reviews = new ArrayList<Review>();

// 이제 ArrayList<Review> 객체 'reviews' 를 intent객체에 넣는다.
intent.putParcelableArrayListExtra("key", reviews);
startActivity(intent);

* A 액티비티로 ArrayList<Review> 보내기

Intent intent = getIntent();
ArrayList<Review> reviews = new ArrayList<Review>();
reviews = intent.getParcelableArrayListExtra("key");

* A 액티비티에서 ArrayList<Reivew> 받아서 사용하기

 

 

위와 같은(2-1, 2-2, 2-3) 방법을 통해, intent에 다양한 data를 담아서 보내고, 받을 수 있다.

이번 파트는 이번 프로젝트 수행에 필수로 알아야하는 내용은 아니지만, 앞으로 앱개발을 함에 있어서

자주 사용될 중요한 코드라고 생각된다. 반/복/숙/달!

 

 

 

3. RatingBar의 값을 사용자가 직접 세팅할 수 있게하자.(작성하기 화면)

 

 

layout.xml 에서 <RatingBar>를 추가하여 화면에서 확인해보면, 사용자가 레이팅바를 눌러도 값이 변하지않고,

그저 초기에 지정된 값이 고정되어 보여진다.

 

하지만 사용자가 직접 터치를 통하여 값을 조정할 수 있게 하고싶으면, 단순히 다음과 같이 <RatingBar>의

android:isIndicator attribute값을 설정해주면 된다.

 

<RatingBar
    ...
    android:isIndicator="false"
    ...
/>

이렇게하면 사용자가 값을 변경할 수 있고, 지정한 값은 연결된 액티비티에서 ratingbar.getRating() 을 사용하여 값을 얻어올 수 있다.

 

 

 

 

4. '작성하기' Activity에서 작성한 한줄평이 '모두보기' 화면의 한줄평 리스트에

추가되어 보일 수 있도록 해보자(필수아님!)

머리아프면 넘기자..

 

 

'작성하기' 화면에서 한줄평을 작성하고, 해당 액티비티가 종료되면

MainActivity 또는 '모두보기' 화면에 있는 한줄평리스트(RecyclerView)에 추가되어 보이도록 하고싶을 때!

이부분을 참고하면 되겠다.

 

이 부분은 프로젝트 상에서 필수로 구현해야 하는 부분은 아니었으나, 어차피 프로젝트를 진행함에 있어서 언젠가는 구현할 것이다. 따라서 미리 해본다는 생각으로 해도 좋겠다.

 

작성하기 Activity에서 intent를 통해 한줄평 데이터를 넘겨줄테니,

해당 데이터를 필요로 하는 액티비티의 onActivityResult() 에 코드를 작성해야 할 것이다.

(실제로 database에 추가하는 내용은 배우지 않았다. 따라서 아래의 코드만으로는 새로 추가한 한줄평내용이 영구히 저장되지 않고, 어플리케이션을 껐다가 켜면 없어지게 된다.)

 

우선, RecyclerView에 사용되는 Adapter와 dataset을 전역변수로 선언해두자.

public class ReviewActivity extends AppCompatActivity {

    RecyclerView recyclerView;
    
    ArrayList<Review> reviews; // 한줄평 데이터들이 담긴 dataset
    ReviewAdapter adapter; // 리사이클러뷰에 사용되는 어댑터

	@Override
	protected void onCreate(){
    	...
        // adapter 변수 할당
        adapter = new ReviewAdapter(getApplicationContext());
        
         // dataset 초기화(예시)
        getReviews(reviews);
        ...
    }
    
    ...
}

 

이제 onActivityResult() 부분을 보자.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {

    switch (requestCode)
    {
        case 1004:
			// 새로운 리뷰 객체 생성
            Review newReview = new Review();
            // dataset에 새로운 리뷰 추가
            reviews.add(newReview);

            // 데이터가 저장된 후, dataset이 변경되었으므로 어댑터에 변경되었음을 알려준다.(새로고침과 같은 개념!)
            adapter.notifyDataSetChanged();
            
            // 여기서부터는 한줄평리스트에 새로운 한줄평 데이터가 추가된 것을 볼 수 있다.
            
            // ** 이거슨 선택사항(아래에서 부연설명) **
            recyclerView.scrollToPosition(adapter.getItemCount()-1);
    }

}

dataset의 내용을 변경한 뒤, 단순히 리사이클러뷰의 어댑터에게 내용이 변경되었음을 알려주면 된다.

메소드의 이름을 보면 굉장히 직관적이다.

굳이 변역해보자면, adapter.notifyDataSetChanged() == 어댑터 . 데이터_변경되었다고_알려줘!()

 

마지막 부분은 선택사항이다.

 

생각을해보면, 인터넷 댓글 또는 SNS게시물 등을 보면 가장 최신의 데이터는 리스트의 최상단에 보여지기 마련이다.

하지만 보통의 리사이클러뷰를 보면, 리스트의 index 0은 리스트의 가장 윗부분에 배치되어있고 내려갈수록 인덱스값이 높아진다. 따라서 이와 같은 구조라면, 최신의 댓글을 보기위해서 매번 아래로 스크롤해서 보아야한다.

이를 해결하기 위해서,

(1) 리사이클러뷰의 아이템이 배열되는 순서를 역순으로 하고,

(2) 새로운 아이템이 추가되었으므로, java코드를 통해 리스트를 가장 위로 스크롤 해주면,

 

가장 최신의 한줄평이 리사이클러뷰의 최상단에 배치되어 보일 수 있도록 할 수 있다!!

 

(1)번은, RecyclerView에 사용되는 LayoutManager를 변경해주면 된다.

그동안 (나는)

LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);

이렇게 해왔는데, 여기서 세번째 파라미터만 true로 변경해주면 리스트가 역순으로 아이템을 배치하게 된다.

LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true);
recyclerView.setLayoutManager(layoutManager);

(1) 은 이렇게 해결!

 

(2)번은, onActivityResult()에서 마지막에 추가한 코드 그대로 사용하면 되겠다.

recyclerView.scrollToPosition(adapter.getItemCount()-1);

scrollToPosition() 메소드를 사용하여 가장 높은 index의 아이템을 보여지게 한다.

 

 



 

 

이렇게 3가지의 핵심포인트와, 프로젝트를 진행함에 있어서 도움이 될만한 4가지 추가 tip 

모두 정리해보았다!

 

최종 PJT3 완성 화면!!!!!

 

이번 프로젝트는 비교적 난이도가 낮고, 강의에서 설명한 서비스와 브로드캐스트 수신자 등의 어려움 개념이 포함되지 않아서

PJT1, PJT2 보다도 쉬운편이었다.

따라서 다룰 내용이 크게 많지 않아 어떤 내용을 넣어야할지 고민이 많았고, 결국 프로젝트 구현에 필수적이지는 않은, 비교적 선택적인 내용이 많이 들어간 것 같다.

하지만 선택적일뿐, 내용자체는 모두 중요하고 필수적인 내용이다.

 

조금 더 완성도 높은 프로젝트를 완성하는데 도움이 되길 바라며..

 

(글쓰는게 생각보다 어렵다ㅜㅜ)

 

이만 끄읏

 

 

 

-  third_floor

+ Recent posts