Android 공부

감자튀김의 다시 쓰는 Android MVVM(2) - DI



-1. 이전 글


2019/03/17 - [Android 공부] - 감자튀김의 다시 쓰는 Android MVVM(1) - DI


0. 서론


해당 프로젝트는 Upbit거래 시세를 알아낼 수 있는 앱을 만드는 것이 목표입니다. 이전에 DI를 사용한 구조에서 ViewModel을 종속할 수 없어서 구조를 변경했습니다. 역시, 이해는 잘 안 되지만, 제가 만든 코드를 분석해보는 시간을 갖겠습니다.


1. 구조의 변경


이전에 생각했던 MVVM과 DI의 구조는 이와 같았습니다.


1.1. 초기에 생각했던 구조

 AndroidSupportInjection, Application, Activity, ViewModel과 같이 MVVM의 큰 구조에 사용되어지는 Module을 모아주는 구조로 해서 각종 모듈을 관리합니다. 하지만, ViewModel에서 Repository를 사용하기 위해서는 적절치 않았다고 생각합니다.


1.1.1. BaseApplication을 통한 DI사용 :

설명을 구체적으로 하자면, BaseApplication은 DaggerApplication을 상속받습니다. Application특성상, 각종 컴포넌트보다 우선순위에 있게 되어 가장 먼저 DaggerApplicationComponent를 build합니다.


1.1.1. ApplicationComponent을 통한 Module 관리 :

ApplicationComponent는 각종 Module을 갖고 있는 객체입니다. 그렇기 때문에 가장 이전에 사용해야할 각종 모듈들을 모아주는 중앙적인 구조가 됩니다.



구조



2. 변경된 구조


2.1 변경점

MainViewModel에서 Repository를 사용하는 것을 중점적으로 구조가 변경되었습니다. 그에 따라서 NetModule과 Repository모듈이 만들어졌습니다.


NetModule에서는 Retrofit2를 생성하기 위해서 HttpLogging과 Retrofit객체를 관리합니다. 그리고 Retrofit을 사용하기 위한 인터페이스를 생성하고, JWT를 넘겨주기 위한 Helper클래스를 관리하도록 객체를 Provide 시켜줍니다.


Repositoy 모듈에서는 각종 api통신들을 ViewModel에서 사용할 수 있도록 repository 패턴으로 객체화하고, 해당 객체를 provide 합니다.



3. 코드


3.1. NetModule


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Module
class NetModule {
    @Provides
    @Singleton
    fun provideHttpLogging(): OkHttpClient {
        val logging = HttpLoggingInterceptor()
        logging.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
            .addInterceptor(logging)
            .build()
    }
 
    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit
            .Builder()
            .baseUrl(BaseUrl.UPBIT.url)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }
 
    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): UpbitAPI = retrofit.create(UpbitAPI::class.java)
 
    @Provides
    @Singleton
    fun provideJJwtHelper() : JJwtHelper = JJwtHelper()
}
cs


위와 같이 HttpLogging을 만들어서 provide해줍니다. 그리고 해당 객체를 provideRetrofit에서는 파라매터를 통해서 받게 되더라도 의존성 주입이 가능합니다. 그리고, OkHttpClient를 받아서 Retrofit객체를 만들어서 provide해줍니다. 또한 이 객체를 provideApiService() 매소드의 파라매터 값을 전달해서 retrofit을 create해줍니다. 이렇게 되면 retrofit을 사용하기 위한 wrapping 객체가 만들어집니다.


또한, UpbitApi를 사용하기 위해서 JWT토큰으로 변환해주는 객체가 필요한데, 이것을 위해서 JJWTHelper클래스를 만들고 provide 해줍니다.


3.2. RepositoryModule


1
2
3
4
5
6
@Module
class RepositoryModule{
    @Provides
    @Singleton
    fun provideUpbitRepository(upbitAPI: UpbitAPI) : UpbitRepositoryImpl= UpbitRepositoryImpl(upbitAPI)
}
cs


RepositoryModule은 위의 NetModule에서 만든 Api를 파라매터로 받아서 upbitRepository 객체를 만들어주는 것입니다.

여기에서 repository란, ViewModel에서 데이터를 관리해야 하지만, 데이터를 가져오는 로직을 ViewModel에 부여할 경우에 너무 많은 기능이 ViewModel에만 부여되는 것을 방지하고자, Repository패턴을 통해서 관심사를 분리시켜주고, ViewModel이 라이프사이클에 의해서 사라지더라도, 네트워크 호출에 대한 내용을 저장할 수 있게 만든 객체입니다.



3.3. Web Service Class


API : https://docs.upbit.com/v1.0.1/reference


1
2
3
4
5
6
7
8
9
10
interface UpbitAPI {
    @GET("v1/market/all/")
    fun getMarketInfo(): Single<List<MarketCode>>
 
    @GET("v1/accounts/")
    fun getMyAccount(@Header("Authorization") jwtToken: String): Single<List<AccountCoin>>
 
    @GET("v1/orderbook/")
    fun getOrderBook(@Query("markets") markets: String): Single<List<OrderBook>>
}
cs


3.4. UpbitRepositoryImpl 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface UpbitRepository {
    fun getMarketCodeAll(): Single<List<MarketCode>>
    fun getOrderBook(market: String): Single<List<OrderBook>>
    fun getMyAccount(jwtToken: String): Single<List<AccountCoin>>
}
 
class UpbitRepositoryImpl @Inject constructor(val upbitAPI: UpbitAPI) : UpbitRepository {
    override fun getMyAccount(jwtToken: String): Single<List<AccountCoin>> {
        return upbitAPI.getMyAccount(jwtToken)
    }
 
    override fun getMarketCodeAll(): Single<List<MarketCode>> {
        return upbitAPI.getMarketInfo()
    }
 
    override fun getOrderBook(market: String): Single<List<OrderBook>> {
        return upbitAPI.getOrderBook(market)
    }
}
cs

WebServiceClass에서 구현한 retrofit통신을 관련해서 Upbit에 대한 다양한 api를 실제로 호출해서 결과값을 받는 레파지토리입니다. 
getMarketCodeAll() : 마켓에 있는 코인에 대한 정보를 가져오는 매소드
getOrderBook(market) : 마켓에 올려져 있는 코인의 호가 정보를 가져오는 매소드
getMyAccount(jwtToken) : 현재 나의 계좌 정보를 가져오는 매소드
로 구성되어 있습니다.

3.4. MainViewModelModule


1
2
3
4
5
6
7
8
9
10
11
12
@Module
abstract class MainViewModelModule {
    @Module
    companion object {
        @JvmStatic
        @Provides
        @ViewModelScope
        fun provideMainViewModel(upbitRepositoryImpl:UpbitRepositoryImpl, jjwtHelper:JJwtHelper): MainViewModel {
            return MainViewModel(upbitRepositoryImpl, jjwtHelper)
        }
    }
}
cs

MainViewModelModule에서는 UpbitRepositoryImpl에 대한 종속성을 받고, JjwtHelper에 대한 종속성을 받아서, MainViewModel 내부에서 해당 로직을 사용할 수 있게 합니다.

3.5. MainViewModel


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface UpbitHelper {
    fun getMarketCodeAll()
    fun getMarketOrder(market: String)
    fun getMyAccount()
 
    fun updateTodayBenefit()
    fun updateMyWallet(accountCoinList: List<AccountCoin>)
}
 
class MainViewModel @Inject constructor(
    private val upbitRepositoryImpl: UpbitRepositoryImpl, private val jjwtHelper: JJwtHelper) : ViewModel(),UpbitHelper 
{
    //ViewModel의 각종 로직
    //...
    //... 
}
cs


MainViewModel에서는 Repository를 inject 받습니다. 그에 따라서 각종 객체를 Observable 하거나 Single로 하여금, API의 값들을 가져올 수 있게 됩니다.


3.6. MainActivity


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MainActivity : DaggerAppCompatActivity() {
    @Inject
    lateinit var mainViewModel: MainViewModel//inject를 통해서 객체를 받음.
    
    override fun onCreate(savedInstanceState: Bundle?) {
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        activityMainBinding
            .apply {
                lifecycleOwner = this@MainActivity
                coinSearchResultAdapter = CoinSearchResultAdapter((this@MainActivity).mainViewModel)
                orderBookRVAdapter = OrderBookRVAdapter()
                mainViewModel = (this@MainActivity).mainViewModel
                mainViewModel?.getMarketCodeAll()
                mainViewModel?.getMyAccount()
            }
    }
}
cs


MainActivity에서는 MainViewModel을 inject를 통해서 받고, DataBinding을 통해서 view를 출력해주고, 각종 DataBining 값을 입력해줍니다. DataBinding에 관련해서는 추후에 다루도록 하겠습니다.


4. 마무리


구조적인 관점에서 얘기를 하기 때문에 각종 중요한 개념들이 많이 부족하게 설명을 하게 되었습니다. 물론 제 지식이 부족한 점도 있겠지만, 다음 편에서 DI에서 사용되어진 Repository 패턴에 관한 이야기에서부터 JWT를 사용하기 위한 방법까지 글을 작성하려고 합니다.


꾸준하게 MVVM에 대해서 공부를 해보도록 하겠습니다.