안드로이드 앱 개발을 하다 보면 Activity 와 Fragment 간의 데이터를 주고 받아야 할 때가 있다. 기존 방식은 어떤 방식을 사용했고 새로운 방식은 또 어떤 방법으로 하는지 알아보자.

기존 방식

  1. Activity → Fragment

    데이터를 넘길 때 Fragment newInstance fuction 을 만들어 Bundle로 데이터를 넘김
    Fragment TAG 를 사용해서 Fragment 를 찾은 다음 Fragment 의 데이터 set

  2. Fragment ←→ Activity

    Fragment 가 Activity 에 attach 되는 순간 context 를 통해서 Listener 구현해서 데이터 공유

  3. Activity 내부 Fragments 간의 데이터 공유

    One Fragment → Activity → Two Fragment
    이렇게 공유 했었다.

새로운 방식

ViewModel 을 사용하면 데이터 공유가 더 쉬워 진다.

Activity

ViewModelProvider(this(액티비티)).get(액티비티 뷰모델명::class.java)

Fragment

private val activityViewModel by lazy {
        //Fragment에서 ViewModelProvider을 생성 할 때 Fragment 를 넣는게 아니라 Activity 가 들어 가야 한다.  
        ViewModelProvider(requireActivity()).get(액티비티 뷰모델명::class.java)
}

Fragment 에서 ActivityViewModel 을 사용하기 때문에 데이터 공유가 훨씬 편해진다.
LiveData 를 사용하면 더 사용하기 좋다.
LiveData Observe 할 때는 ViewModelProvider 을 생성 할 때와 달리 각각의 라이프사이클에 맞춰서 셋팅 해주면 된다.

Activity → ActivityViewModel
Fragment in Activity→ ActivityViewModel
OtherFragment in Activity → ActivityViewModel

위와 같이 ActivityViewModel을 공유하기 때문에 데이터 공유가 자유로워 진다.

코인이란?

코틀린 개발자를 위한 실용적인 경량 종속성 주입 프레임 워크이다.
코인은 도메인 특화 언어인 DSL(Domain Specific Language) 이다.

Koin DSL

applicationContext : Koin 모듈 생성
factory : 매번 inject 할때마다 인스턴스를 생성
single : 싱글톤으로 생성
bind : 종속시킬 class 나 interface를 주입
get : 컴포넌트내에 알맞은 의존성 주입

의존성 주입을 하기 위한 라이브러리는 대거2 도 있지만 코인은 좀 더 사용하기 쉽고 가볍게 만들어 졌기 때문에 러닝커브가 낮다고 할 수 있다.

우선 코인을 사용하기 위해 앱 모듈 단에서 dependency를 추가 해준다.

dependencies {
    //현재 최신버전인 1.0.2 버전으로 사용하였다.
    implementation "org.koin:koin-android:1.0.2" 
}

다음 예제 코드를 보자.

class TestApplication : Application() { //Application 클래스에서 모듈과 startKoin을 호출 해준다.

    private val goodsModule = module {
        factory { GoodsData() } //상품데이터 생성
        single { GoodsServiceImpl(get()) as GoodsService } 
        //GoodsService는 싱글톤으로 생성 되고  get() 은 컴포넌트내에 알맞은 의존성 주입을 해준다.
        //get() 은 GoodsData 라고 생각 하면 된다.
        factory {
            //FactoryData 가 생성 될 때마다 변경되는 값을 넣기 위해 현재시간(Long 타입)을 사용
            return@factory FactoryData(Calendar.getInstance().timeInMillis)
        }
    }

    override fun onCreate() {
        super.onCreate()
        startKoin(this, listOf(goodsModule)) //코인 시작!! listOf 에는 다른 모듈도 정의 한다면 뒤에 더 붙여서 사용
    }

}

//FactoryData 데이터 클래스
data class FactoryData(val timeInMillis: Long = 0L)

//GoodsData 데이터 클래스
data class GoodsData(var name : String = "청바지", var price : Long = 30000)

interface GoodsService {
    fun getGoodsInfo(): String
}

class GoodsServiceImpl(private val goodsData: GoodsData) : GoodsService {

    override fun getGoodsInfo(): String {
        //기본적으로 생성 되는 goodsData 정보는 상품명 청바지 , 가격 30000
        return "상품명 : " + goodsData.name + "  가격 : " + goodsData.price
    }
}

class MainActivity : AppCompatActivity() {
    private val goodsService: GoodsService by inject()  //by inject() 를 사용해서 GoodsService 주입
    private val goodsData: GoodsData by inject() //by inject() 를 사용해서 GoodsData 주입
    private val factoryData: FactoryData by inject() //by inject() 를 사용해서 FactoryData 주입

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

}

Koin 사용 방법

  1. Application 부분에서 모듈 정의를 해주고 Application onCreate 시점에서 Startkoin을 호출 해주면서 사용 할 module 을 정의해준다.
  2. module을 정의 할 때 데이터를 어떤 방식으로 생성할지 따로 정의를 해준다
    ex) factory , single , bind, get
  3. 실제 사용 되는 부분에서 주입 하기 위해 by inject() 를 통해 주입한다.

다음 편에서는 Android 에서 사용되는 ViewModel에 의존성 주입하는 방법을 알아보자.

프록시 패턴 정의

어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴이다.

프록시 패턴 언제 사용할까!?

원격 프록시 , 가상 프록시 , 보호 프록시 여러가지가 있는데 가상 프록시를 다뤄보자.
네트워크를 통해 이미지를 가져온다고 가정할 때 네트워크 상태와 인터넷 연결 속도에 따라서 시간 소요 되게 된다.
이때 이미지를 불러 오는 동안 Proxy를 사용해서 임의의 이미지를 보여준 다음 실제 이미지를 보여줄 수 있다.
아래 코드를 보자.

class ProxyPattern {
    @Test
    fun test() {
        ImageProxy().showIcon()
    }
}

interface Icon {
    fun showIcon()
}

class ImageIcon : Icon {

    override fun showIcon() {
        download()
        println("ImageIcon 그림")
    }

    private fun download() {
        println("ImageIcon 다운로드")
    }

}

class ImageProxy : Icon {
    var imageIcon: ImageIcon? = null

    override fun showIcon() {
        println("ImageProxy 그림")
        imageIcon = ImageIcon()
        imageIcon?.showIcon()
    }
}

ImageProxy 를 통해 showIcon 메소드를 호출 하게 되면 프록시 임의의 그림을 보여주고 다운로드가 되었을때 실제 이미지 ImageIcon 그림을 보여줄 수 있다.

아래는 테스트 결과이다.

스테이트 패턴 정의

객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.
객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

스테이트 패턴 언제 사용할까!?

현재 기분 상태를 예를 들어 보자 Good , Bad , Normal 상태가 있다.
그리고 rain (비가 옴), clear (맑음) , vacation (휴가) 메소드가 있다.
비가 옴 , 맑음 , 휴가 일때 상태가 변경 된다.
아래의 코드를 보자.

class StatePattern {
    @Test
    fun test() {
        val stateContext = StateContext()
        stateContext.state = BadState(stateContext)
        stateContext.clear()
        stateContext.rain()
        stateContext.vacation()
        stateContext.rain()
    }

    interface State {
        fun rain()
        fun clear()
        fun vacation()
    }

    class GoodState(private val stateContext: StateContext) : State {
        override fun rain() {
            println("비가와서 기분 좋음에서 보통으로")
            stateContext.state = stateContext.normalState
        }
        override fun clear() {
            println("날씨 맑아서 기분 좋지만 최상의 상태")
        }
        override fun vacation() {
            println("휴가다~~! ^^ 하지만 최상의 상태")
        }
    }

    class NormalState(private val stateContext: StateContext) : State {
        override fun rain() {
            println("비가와서 기분 보통에서 나쁨으로")
            stateContext.state = stateContext.badState
        }
        override fun clear() {
            println("날씨 맑아서 기분 보통에서 좋음 변경")
            stateContext.state = stateContext.goodState
        }
        override fun vacation() {
            println("휴가다~~! ^^ 기분 보통에서 좋음 변경")
            stateContext.state = stateContext.goodState
        }
    }

    class BadState(private val stateContext: StateContext) : State {
        override fun rain() {
            println("비가 오지만 이보다 더 안좋을 수 없다.")
        }
        override fun clear() {
            println("날씨 맑아서 기분 안좋음에서 보통으로 변경")
            stateContext.state = stateContext.normalState
        }
        override fun vacation() {
            println("휴가다~~! ^^ 기분 안좋음에서 좋음으로 두단계 업!!")
            stateContext.state = stateContext.goodState
        }
    }


    class StateContext {
        var state: State? = null
        var goodState = GoodState(this)
        var normalState = NormalState(this)
        var badState = BadState(this)
        fun rain() {
            state?.rain()
        }
        fun clear() {
            state?.clear()
        }
        fun vacation() {
            state?.vacation()
        }
    }
}

얼핏 보면 스트레티지 패턴과 동일하다고 볼 수 있겠지만 차이점이 있다.
스트레티지 패턴의 경우는 어떤 알고리즘의 행동 자체를 건네주는 반면 스테이트 패턴의 경우는 비슷하긴 하지만 미리 정해진 상태 전환 규칙을 바탕으로 알아서 자기 상태를 변경하는 점이 다르다.
구조가 비슷하긴 하지만 용도나 목적이 완전 다르다.

아래는 테스트 결과이다.

참조 링크 https://en.wikipedia.org/wiki/State_pattern

컴포지트 패턴 정의

객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있습니다.
이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있다.

컴포지트 패턴 언제 사용할까!?

식당에 메뉴가 있는데 아침 메뉴 점심 메뉴 저녁메뉴가 있을 수 있고 또 다른 디저트 메뉴 혹은 디저트 메뉴 안에서도 또 다른 메뉴로 나뉘어 질 수 있다.
이런 식으로 계층 구조로 생각 해야 하는 경우 사용하면 유용하다.

아래의 코드를 보자.


class CompositePattern {
    @Test
    fun test() {
        val allMenus = Menu("전체메뉴")

        val breakfastMenu = Menu("아침메뉴").apply {
            menuComponents.apply {
                add(MenuItem("토스트", 3000))
                add(MenuItem("삼겹살", 10000))
                add(MenuItem("김밥", 2500))
            }
        }

        val lunchMenu = Menu("점심메뉴").apply {
            menuComponents.apply {
                add(MenuItem("ㅄㅎㄱ", 10000))
                add(MenuItem("ㅍㅇㅈ", 3500))
            }
        }

        allMenus.menuComponents.apply {
            add(breakfastMenu)
            add(lunchMenu)
        }

        allMenus.print()

    }

    //Component
    abstract class MenuComponent {
        abstract fun print()
    }

    //Leaf
    class MenuItem(private val name: String, private val price: Int) : MenuComponent() {
        override fun print() {
            println("name : $name , price : $price")
        }
    }

    //Composite
    class Menu(private val name: String) : MenuComponent() {
        val menuComponents = arrayListOf<MenuComponent>()

        override fun print() {
            println("name : $name")
            val iterator = menuComponents.iterator()
            while (iterator.hasNext()) {
                val menuComponent = iterator.next()
                menuComponent.print()
            }
        }
    }
}

Leaf 에서는 더 이상 메뉴를 추가,제거 할 수 없다.
Composite 에서만 가능

아래는 테스트 결과이다.

[사진 자료 출처] (https://ko.wikipedia.org/wiki/%EC%BB%B4%ED%8F%AC%EC%A7%80%ED%8A%B8_%ED%8C%A8%ED%84%B4#:~:text=%EC%BB%B4%ED%8F%AC%EC%A7%80%ED%8A%B8%20%ED%8C%A8%ED%84%B4(Composite%20pattern)%EC%9D%B4%EB%9E%80,%EB%8F%99%EC%9D%BC%ED%95%98%EA%B2%8C%20%EB%8B%A4%EB%A3%A8%EB%8F%84%EB%A1%9D%20%ED%95%9C%EB%8B%A4.)