본문 바로가기
Development/Android

[Android] Fragment로 Activity 구성 및 통신 - 3

by du.it.ddu 2020. 5. 24.
반응형

이전 포스팅에서 Interface를 활용하여 Activity와 Fragment의 통신을 구현 해 보았다.

이번 포스팅은 예고했던 대로 EventBus와 ViewModel을 활용하는 방법을 소개 할 것이다.

EventBus -> ViewModel 순으로 소개한다.


- EventBus 라이브러리

EventBus라이브러리는 아래 두 가지가 있다. 

1. Square사의 Otto (https://github.com/square/otto)

2. greenrobot의 EventBus (https://github.com/greenrobot/EventBus)

이 예제에서는 2번의 greenrobot의 EventBus를 사용한다.

Github 링크를 통해 라이브러리에 대해 알아보는 것을 추천한다.


- EventBus란?

EventBus는 브로드캐스트를 사용 해 봤다면 이해하기 쉬울 것이다.

브로드 캐스트를 등록 해 놓은 액티비티는 브로드 캐스트 리시버 객체의 onReceive 콜백의 Intent를 통해 데이터를 전달받는다. 여러개가 등록되어 있다면 분기도 필요하다.

Activity와 Fragment의 통신도 브로드캐스트를 활용할 수 있지만, 인텐트에 클래스 객체를 전달하려면 Parcelable을 사용해야 하는 귀찮은 점이 존재하다.

greenrobot의 EventBus는 링크에도 잘 나와있지만 간단하게 설명하면 아래와 같다.

구독자(Subscriber)는 EventBus에 자신을 등록하고, 원하는 Event에 대한 콜백함수를 정의한다. 발행자(Publisher)가 특정 Event를 Post 하면 구독자들에게 전달되고, 등록한 함수에 의해 처리된다.

Event는 클래스 객체이며 다양한 데이터를 가질 수 있기 때문에 브로드 캐스트 리시버처럼 Parcelable를 사용 할 필요없이 간단하며 유연하다.

간단하게 생각하자면 브로드 캐스트와 유사하게 이벤트를 등록하고 발행된 것을 받아 콜백함수에서 처리하는 방식이라고 볼 수 있겠다.


- Gradle에 EventBus 추가

글 작성 기준, Github에서 implementation 'org.greenrobot:eventbus:3.2.0' 를 추가 할 것을 가이드하고 있다.

build.gradle(Module: app)의 dependencies에 아래와 같이 추가해주자.

...

android {
    ...
}

dependencies {
    ...

    implementation 'org.greenrobot:eventbus:3.2.0'
}

- Event를 위한 Class 작성

data class PetPurchaseEvent(
    val type: PetType
) {
}

PetPruchaseEvent 라는 이름을 가진 data class를 정의했다. 현재는 type만 갖고 있다.

data class에 대해선 생략한다.

추후 코틀린에 대해 포스팅할 때 자세히 다뤄보자.


- Activity에 EventBus 적용

class MainActivity : AppCompatActivity() {

	...

    override fun onCreate(savedInstanceState: Bundle?) {
    	...
    }

    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        super.onStop()
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onPetPurchaseEvent(event: PetPurchaseEvent) {
        when(event.type) {
            PetType.Dog -> dogCount += 1
            PetType.Cat -> catCount += 1
            PetType.Rabbit -> rabbitCount += 1
        }
        updatePetCount()
    }

    private fun initFragments() {
    	...
    }

    private fun commitFragment(fragment: Fragment, containerResId: Int) {
    	...
    }

//    override fun onPetPurchase(type: PetType) {
    	...
//    }

    private fun updatePetCount() {
    	...
    }

    private fun petCountToString() : String {
    	...
    }
}

 이전 포스팅에서 OnPetEventListener와 관련하여 작성했던 코드들은 주석처리나 제거를 했다.

Fragment를 생성할 때 listener를 세팅해주던 코드도 마찬가지다.

EventBus와 관련하여 onStart, onStop에서 EventBus를 등록, 해제하는 코드를 추가하였다.

그리고 onPetPurchaseEvent에서 PetPurchaseEvent를 전달받는 함수를 등록하였고 Annotation을 사용하였다.

이벤트가 발생하면 TextView를 수정하여야 하므로 threadMode는 메인 쓰레드로 설정한다.


- Fragment에 EventBus 적용

class DogFragment : Fragment(){

//    var listener: OnPetEventListener? = null

    override fun onCreateView(
    	...
    ): View? {
        return inflater.inflate(R.layout.frag_dog, null).apply {
            bt_dog.setOnClickListener {
//                listener?.onPetPurchase(PetType.Dog)
                EventBus.getDefault().post(PetPurchaseEvent(type = PetType.Dog))
            }
        }
    }
}

각각의 Fragment를 위와 같은 형식으로 수정한다.

listener 객체를 주석처리 하여 사용하지 않고, 대신 버튼을 누르면 EventBus를 통해 PetPurchaseEvent를 post한다.

이제 끝났다. 동작을 확인하자.


- 실행 결과

실행 후 각 버튼을 누르면 동일한 동작을 하는 것을 알 수 있다.

Interface를 활용하는 것 보다 훨씬 간단하게 구현 된 것 같다. (나만 그런가)

마지막으로 ViewModel을 활용하는 것으로 변경해본다.


- ViewModel을 사용할 수 있는 이유

ViewModel은 Model - View Model - View 로 구성된 MVVM 아키텍처 패턴에 대해 공부했다면 익숙 할 것이다.

Android에서 ViewModel은 viewModel = ViewModel()와 같이 사용하는 것이 아닌, ViewModelProvider에 의해 생성되고 관리된다.

ViewModel의 수명주기는 ViewModelProvider에 전달되는 Lifecycle로 지정되는데, Activity를 전달했다면 전달된 Activity가 파괴되어 사라지고 나서야 사라진다는 것이다. 다른 말로 하면 한번 생성된 Activity가 살아있는 동안은 몇 번을 생성하더라도 같은 ViewModel을 생성할 수 있다는 것이고, 데이터가 유지된다는 것이다.

우리는 살아있는 Activity에 Fragment를 생성하고 결합된다. 그리고 Fragment는 getActivity()를 통해 자신이 결합한 Activity를 알 수 있고, ViewModel을 생성할 때 Activity를 전달하면 같은 ViewModel을 공유할 수 있다는 것이다. 이 공유된 ViewModel을 통해 Activity와 Fragment의 통신을 구현할 수 있다.


- Gradle에 ViewModel, LiveData 추가

https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ko

 

 

Lifecycle  |  Android 개발자  |  Android Developers

수명 주기 인식 구성요소는 활동 및 프래그먼트와 같은 다른 구성요소의 수명 주기 상태 변경에 따라 작업을 실행합니다. 이러한 구성요소를 사용하면 잘 구성된 경량의 코드를 만들어 더욱 쉽�

developer.android.com

위 링크를 참고하여 viewModel과 LiveData를 추가 해 주자.

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 등과 같이 추가해주면 되고, 아래와 같은 형태가 될 것이다.

...

android {
    ...
}

dependencies {
    ...
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}

- PetPurchaseModel Class 작성

data class PetPurchaseModel(
    var dogCount: Int = 0,
    var catCount: Int = 0,
    var rabbitCount: Int = 0
) {
}

MainAcitivty에서 사용했던 Count 변수들을 담는 Model Class를 작성한다.

ViewModel에서 이 Model을 사용 할 것이다.

- PetPurchaseViewModel Class 작성

class PetPurchaseViewModel : ViewModel() {
    private val _petPurchaseModel = MutableLiveData<PetPurchaseModel>(PetPurchaseModel())
    val petPurchaseModel: LiveData<PetPurchaseModel>
        get() = _petPurchaseModel

    fun purchase(petType: PetType) {
        _petPurchaseModel.value?.run {
            when(petType) {
                PetType.Dog -> dogCount += 1
                PetType.Cat -> catCount += 1
                PetType.Rabbit -> rabbitCount += 1
            }
            _petPurchaseModel.postValue(this)
        }
    }
}

위와 같이 작성한다.

_petPurchaseModel는 MutableLiveData로 선언한다. 따라서 변경이 가능하므로 외부에서 접근을 막고 ViewModel 내에서만 변경하도록 한다.

그리고 외부에서는 petPurchaseModel를 LiveData로 하여 변경이 불가능 하도록 하고 Observing할 수 있도록 한다. 

petPurchaseModel가 _petPurchaseModel를 반환하게 되어 있으므로 데이터는 동일하다.

purchase 메서드에 PetType를 전달하면 현재의 PetPurchaseModel을 가져와 연산 후 변경된 데이터를 post해 준다.


- MainActivity 수정

class MainActivity : AppCompatActivity() {
	...

    private lateinit var viewModel: PetPurchaseViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initViewModel()
        initFragments()
    }

    private fun initViewModel() {
        viewModel = ViewModelProvider(this).get(PetPurchaseViewModel::class.java)

        viewModel.petPurchaseModel.observe(this, Observer {
            updatePetCount(it)
        })
    }
    
    ...

    private fun updatePetCount(model: PetPurchaseModel) {
        tv_pet_count.text = "강아지 : ${model.dogCount}\n" +
                "고양이 : ${model.catCount}\n" +
                "토끼 : ${model.rabbitCount}"
    }

기존에 MainActivity에 존재하던 Count 변수들을 제거하고 viewModel을 추가하였다.

그리고 ViewModel을 생성하고 petPurchaseModel을 Observing 한다. 데이터의 변경이 감지되면 updatePetCount에 변경된 데이터를 전달하고 TextView를 변경하도록 하였다.


- Fragment 수정

class DogFragment : Fragment(){

    private lateinit var viewModel: PetPurchaseViewModel

    override fun onCreateView(
    ...
    ): View? {
        activity?.run {
            viewModel = ViewModelProvider(this).get(PetPurchaseViewModel::class.java)
        }

        return inflater.inflate(R.layout.frag_dog, null).apply {
            bt_dog.setOnClickListener {
                viewModel.purchase(PetType.Dog)
            }
        }
    }
}

Fragment들도 위와 같은 형태로 변경한다.

Fragment의 activity가 null이 아닌 경우 ViewModelProvider에 Activity를 전달하여 ViewModel을 생성한다.

버튼을 클릭하면 Fragment에 맞는 PetType를 purchas 메서드에 전달한다.

이후 앱을 실행하면 동일한 결과를 얻을 수 있다.


- 마치며

Activity와 Fragment를 통신하는 방법은 굉장히 다양하다. 포스팅을 하며 Interface, EventBus, ViewModel 세 가지를 소개하였다. 

어떤 앱을 개발하고 어떻게 설계하느냐에 따라 적합한 방식이 있을 것이고 위 세 가지 외에도 더 많은 방법들이 있을 것이다.

다양한 방법을 알고 있으면 적재적소에 활용할 수 있으니 직접 사용해보면서 장단점을 비교해가며 사용하는 것이 좋을 것이다.

반응형