$30 off During Our Annual Pro Sale. View Details »

Advanced MVP(refactoring MVP)

Avatar for Gorita Gorita
April 05, 2019

Advanced MVP(refactoring MVP)

MVP 패턴의 의도에 대해 다시 알아보고 해당 의도를 더 드러낼 수 있도록 리팩토링한 경험에 대해 공유하고자 합니다.

Avatar for Gorita

Gorita

April 05, 2019
Tweet

More Decks by Gorita

Other Decks in Technology

Transcript

  1. )PXUPJNQMFNFOU HPPHMFTBNQMF public interface AddEditTaskContract { interface View {…} interface

    Presenter {…} } public class AddEditTaskPresenter implements AddEditTaskContract.Presenter { private final AddEditTaskContract.View mAddTaskView; } public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View { private AddEditTaskContract.Presenter mPresenter; } $POUSBDUীࢲ7JFX৬1SFTFOUFS JOUFSGBDFܳ੿੄ $POUSBDU7JFXܳҳഅೞҊ JOUFSGBDFܳా೧QSFTFOUFS৬ాन $POUSBDU1SFTFOUFSܳҳഅೞҊ JOUFSGBDFܳా೧WJFX৬ాन
  2. .7$UP.71 XIZ   .7$7JFX $POUSPMMFS .PEFM  ৵൨ٜ঻঻૑ View

    Controller Model User input Modifies Updates xml? activity? fragment?
  3. .7$UP.71 XIZ   .7$7JFX $POUSPMMFS .PEFM  ৵൨ٜ঻঻૑ View

    Controller Model User input Modifies xml? activity? fragment?
  4. .7$UP.71 XIZ   .7$7JFX $POUSPMMFS .PEFM  ৵൨ٜ঻঻૑ View

    Controller Model User input Modifies xml? activity? fragment? Updates MVC(android)
  5. .7$UP.71 XIZ   .7$7JFX $POUSPMMFS .PEFM  ৵൨ٜ঻঻૑ View

    Controller Model User input Modifies xml? activity? fragment? Updates MVC(android)
  6. .7$UP.71 XIZ   .7$7JFX $POUSPMMFS .PEFM  ৵൨ٜ঻঻૑ 

    ࠺؀೧૑ח$POUSPMMFSਬ૑ࠁࣻоয۰਑  $POUSPMMFSо"OESPJE੄ઓࢿ੉֫ইVOJUపझ౟оয۰਑
  7. 4P -FUsT45"35  7JFX 1SFTFOUFS .PEFM੄ҙब੄ܻ࠙ܳઑӘ؊ݺഛೞѱೡࣻ੓׮  7JFX৬QSFTFOUFSܳוटೞѱ JOUFSGBDF োѾ

     "OESPJE੄ઓࢿ9੗زചVOJUపझ౟оਊ੉ೞ׮  .71ಁఢ੄੄بܳ؊૑ఆࣻ੓ب۾  WJFX QSFTFOUFS NPEFMܻಂష݂  6OJUUFTUܻܳಂష݂
  8. 7JFX 1SFTFOUFS .PEFM  t੼੼7JFX੄௏٘۝੉݆ই૓׮u View View .7$YNM .71YNM BDUJWJUZ

    GSBHNFOU BDUJWJUZ੄ઓ੤۽ੋ೧7JFX੄௏٘۝੉݆ই૗যڌѱ೧Ѿೡөਃ
  9. 7JFX 1SFTFOUFS .PEFM  WJFXחQBTTJWFೞѱ੘ࢿ೧ঠೠ׮  QBTTJWFೞѱ੘ࢿೞݶ   ߸زоמࢿ੉о੢௾VJࠗ࠙ীࢲ۽૒੉ܻ࠙ػ׮

     పझ౟оয۰਍VJ۽ࠗఠ۽૒ਸܻ࠙೧೧׼۽૒੄VOJUపझ౟оמ যڌѱQBTTJWFೞѱ੘ࢿೡࣻ੓ਸөਃ  ӓױ੸ਵ۽QBTTJWFೞѱ੘ࢿೞחߑߨਸా೧ೞաঀঌইࠁѷणפ׮
  10. 7JFXQBTTJWF ۽૒ܻ࠙ confirmButton.setOnClickListener { if (titleEditText.text.isBlank()) presenter.showMessageEmptyError() else { presenter.setInputMessage(…)

    } } confirmButton.setOnClickListener { presenter.onConfirmButtonClicked(…) }  ৉ೡ۽૒ VTFSJOQVU੹׳  VOJUపझ౟ࠛоמ  ৉ೡVTFSJOQVU੹׳  VOJUపझ౟QSFTFOUFSܳా೧оמ
  11. 7JFXQBTTJWF 1SFTFOUFSҳઑܳށۄঠೠ׮ override fun onCreate(…) { super.onCreate(…) setContentView(…) refreshButton.setOnClickListener {

    presenter.getItemList() } presenter.getItemList() } override fun onCreate(…) { super.onCreate(…) setContentView(…) refreshButton.setOnClickListener { presenter.refreshButtonClicked() } presenter.onCreate() }  ৉ೡVTFSJOQVU੹׳ ۽૒  VOJUపझ౟ ୶о۽૒ਸҊ۰ оמ  ৉ೡVTFSJOQVU੹׳  VOJUపझ౟VTFSJOQVUӝ߈ਵ۽оמ
  12. 7JFXQBTTJWF JOUFSGBDFٍীऀӝ૑݈ӝ fun onCreate() { view.showLoading() … } QSFTFOUFSܳഐ୹ೞחद੼ࡺ݅ইפۄ QSFTFOUFSী੄೧प೯غחೣࣻউীࢲب۽૒ܻ࠙

    fun showLoading() { progressBar.visibility = View.VISIBLE errorView.visibility = View.INVISIBLE guideView.visibility = View.INVISIBLE } Presenter view fun onCreate() { showLoading() … } private fun showLoading() { view.showProgressBar() view.hideErrorView() view.hideGuideView() } Presenter fun showProgressBar() { progressBar.visibility = View.VISIBLE } view ೞա੄WJFX੄࢚క߸҃ਸৈ۞ೣࣻীࢲ૓೯ؼ҃਋ ҙܻ੄য۰਑੉ߊࢤ
  13.  1SFTFOUFS  VJ۽૒  WJFX৬JOUFSBDU  .PEFMҗJOUFSBDU  .PEFM

     ࠺ૉפझبݫੋ۽૒ 7JFX 1SFTFOUFS .PEFM
  14.  1SFTFOUFS  VJ۽૒  WJFX৬JOUFSBDU  .PEFMҗJOUFSBDU  .PEFM

     ࠺ૉפझبݫੋ۽૒ ৉ೡ੉؊ݺഛ೧૓׮ ௏٘ࠂ੟ب  WJFXܳӒܻӝਤೠVJ۽૒݅Ҋ۰ 7JFX 1SFTFOUFS .PEFM
  15. .PEFMSFQPTJUPSZ VTFDBTF class GetUserBankAccount( private val repository: UserBankAccountsRepository, … )

    : SingleUseCase<UserBankAccount>(…, …) { lateinit var userBankAccountId: String override fun buildUseCaseSingle(): Single<UserBankAccount> = repository.get(userBankAccountId) } interface UserBankAccountsRepository : Repository { fun get(bankAccountId: String): Single<UserBankAccount> … } class UserBankAccountsRepository(private val context: Context) : UserBankAccountsRepository { override fun get(bankAccountId: String): Single<UserBankAccount> = context.retrofit.userBankAccountsApi .getUserBankAccount(bankAccountId) .map { UserBankAccountResponseMapper.from(it.userBankAccount) } }
  16. .PEFMSFQPTJUPSZ VTFDBTF class GetUserBankAccount( private val repository: UserBankAccountsRepository, … )

    : SingleUseCase<UserBankAccount>(…, …) { lateinit var userBankAccountId: String override fun buildUseCaseSingle(): Single<UserBankAccount> = repository.get(userBankAccountId) } interface UserBankAccountsRepository : Repository { fun get(bankAccountId: String): Single<UserBankAccount> … } class UserBankAccountsRepository(private val context: Context) : UserBankAccountsRepository { override fun get(bankAccountId: String): Single<UserBankAccount> = context.retrofit.userBankAccountsApi .getUserBankAccount(bankAccountId) .map { UserBankAccountResponseMapper.from(it.userBankAccount) } }
  17. .PEFMSFQPTJUPSZ VTFDBTF class GetUserBankAccount( private val repository: UserBankAccountsRepository, … )

    : SingleUseCase<UserBankAccount>(…, …) { lateinit var userBankAccountId: String override fun buildUseCaseSingle(): Single<UserBankAccount> = repository.get(userBankAccountId) } interface UserBankAccountsRepository : Repository { fun get(bankAccountId: String): Single<UserBankAccount> … } class UserBankAccountsRepository(private val context: Context) : UserBankAccountsRepository { override fun get(bankAccountId: String): Single<UserBankAccount> = context.retrofit.userBankAccountsApi .getUserBankAccount(bankAccountId) .map { UserBankAccountResponseMapper.from(it.userBankAccount) } }
  18. .PEFMSFQPTJUPSZ VTFDBTF class GetUserBankAccount( private val repository: UserBankAccountsRepository, … )

    : SingleUseCase<UserBankAccount>(…, …) { lateinit var userBankAccountId: String override fun buildUseCaseSingle(): Single<UserBankAccount> = repository.get(userBankAccountId) } interface UserBankAccountsRepository : Repository { fun get(bankAccountId: String): Single<UserBankAccount … } class UserBankAccountsRepository(private val context: Context) : UserBankAccountsRepository { override fun get(bankAccountId: String): Single<UserBankAccount> = context.retrofit.userBankAccountsApi .getUserBankAccount(bankAccountId) .map { UserBankAccountResponseMapper.from(it.userBankAccount) } }
  19. SFQPTJUPSZҳഅ୓ীઓ੤ class GetUserBankAccount( private val repository: UserBankAccountsRepository, … ) :

    SingleUseCase<UserBankAccount>(…, …) { lateinit var userBankAccountId: String override fun buildUseCaseSingle(): Single<UserBankAccount> = repository.get(userBankAccountId) } SFQPTJUPSZ৬VTFDBTFопп੄৉ೡਸࣻ೯ೠ׮
  20. SFQPTJUPSZҳഅ୓ীઓ੤ interface UserBankAccountsRepository : Repository { fun get(bankAccountId: String): Single<UserBankAccount>

    fun getDeleted(): Single<List<UserBankAccount>> fun getHidden(): Single<List<UserBankAccount>> … } VTFDBTFী੓যঠೞח పझ౟غযঠೞח MPHJD੉SFQPTJUPSZೣࣻղࠗীઓ੤
  21. SFQPTJUPSZҳഅ୓ীઓ੤ SFQPTJUPSZ੄ҳഅ୓੄҃਋BOESPJEBQJ੄ઓࢿਵ۽ੋ೧VOJUపझ౟оࠛоמೣ VTFDBTFী੓যঠೞח పझ౟غযঠೞח MPHJD੉SFQPTJUPSZೣࣻղࠗীઓ੤ interface UserBankAccountsRepository : Repository {

    fun get(bankAccountId: String): Single<UserBankAccount> fun getDeleted(): Single<List<UserBankAccount>> fun getHidden(): Single<List<UserBankAccount>> … } QSFTFOUFSীࢲపझ౟೧ঠغח۽૒ਸWJFX੄ೣࣻ۽ਤ੐ೞחѪҗ࠺तೠपࣻ
  22. 1SFTFOUFSীઓ੤ SFQPTJUPSZՙܻ੄Ѿ೤җؘ੉ఠоҕ੉VTFDBTFղࠗীࢲੌযթ class ConfigurationService( private val userRepository: UserRepository, private val

    userDeviceRepository: UserDeviceRepository, private val firebaseRepository: FirebaseRepository, private val organizationsRepository: OrganizationsRepository, ) : CompletableUseCase(…, …) { override fun buildUseCaseCompletable(): Completable = organizationsRepository.initialize() .andThen( Single.zip( userDeviceRepository.getCertificatedCount(), firebaseRepository.getFirebaseToken().toSingle(""), BiFunction { certificatedCount: Int, firebaseToken: String -> … } ).flatMapCompletable { userDeviceRepository.upsertUserDevice(…) } ).andThen(userRepository.syncUser()) } (PPEDBTF
  23. 1SFTFOUFSীઓ੤ SFQPTJUPSZՙܻ੄Ѿ೤җؘ੉ఠоҕ੉QSFTFOUFSীࢲੌযթ private val syncUser: SyncUser = SyncUser(userRepository) private val

    syncUserDevice: SyncUserDevice = SyncUserDevice(userDeviceRepository, firebaseRepository) private val fetchOrganizations: FetchOrganizations = FetchOrganizations(OrganizationsRepository(context)) … #BEDBTF syncUser.execute(object : DefaultCompletableObserver() { override fun onComplete() { syncUserDevice() } } private fun syncUserDevice() { syncUserDevice.execute(object : DefaultCompletableObserver() { override fun onComplete() { … } } }
  24. 1SFTFOUFSীઓ੤ SFQPTJUPSZՙܻ੄Ѿ೤җؘ੉ఠоҕ੉QSFTFOUFSীࢲੌযթ private val syncUser: SyncUser = SyncUser(userRepository) private val

    syncUserDevice: SyncUserDevice = SyncUserDevice(userDeviceRepository, firebaseRepository) private val fetchOrganizations: FetchOrganizations = FetchOrganizations(OrganizationsRepository(context)) … #BEDBTF syncUser.execute(object : DefaultCompletableObserver() { override fun onComplete() { syncUserDevice() } } private fun syncUserDevice() { syncUserDevice.execute(object : DefaultCompletableObserver() { override fun onComplete() { … } } } 1SFTFOUFS੄௏٘۝җࠂ੟بо
  25. 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF @Test fun destroy() { … } @Test fun getUserAssets()

    { … } @Test fun onAllButtonChecked() { … } @Test fun onAllButtonChecked() { … } @Test fun onDepositButtonChecked() { … } 5FTUDPEFԝԝೞѱ੘ࢿ FOUJUZ NPEFM чೞա߸҃ೞפj
  26. @Test fun destroy() { … } @Test fun getUserAssets() {

    … } @Test fun onAllButtonChecked() { … } @Test fun onAllButtonChecked() { … } @Test fun onDepositButtonChecked() { … } ❌ ❌ ❌ ݆਷పझ౟௏٘पಁ ੹ࠗࣻ੿ೞӝীח ߓࠁ׮ߓԞ੉ցޖ௼׮Ҋࢤп੉ٜٸ 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF
  27. /* @Test fun destroy() { … } @Test fun getUserAssets()

    { … } @Test fun onAllButtonChecked() { … } @Test fun onAllButtonChecked() { … } @Test fun onDepositButtonChecked() { … } */ $Jాҗܳਤ೧઱ࢳ୊ܻೞҊ 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF
  28. // TODO: apply new codes /* @Test fun destroy() {

    … } @Test fun getUserAssets() { … } @Test fun onAllButtonChecked() { … } @Test fun onAllButtonChecked() { … } @Test fun onDepositButtonChecked() { … } */ $Jాҗܳਤ೧઱ࢳ୊ܻೞҊ ݃૑݄নबਵ۽50%0௏ݭ౟ܳ୶о 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF
  29. private val mockName = "user" private val mockPhoneNumber = "01012341234"

    private val mockRequestId = "testId" ߸ࣻী੄޷হחчਸࢶ঱ೞѢա 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF పझ౟ܳਤ೧
  30. private val mockName = "user" private val mockPhoneNumber = "01012341234"

    private val mockRequestId = "testId" 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF UserExpenseTransaction( "id", Amount(1000.0, ""), "title", null, testDate, TransactionProduct(“productid", "type", "name"), TransactionCategorySummary("", "", TransactionType.EXPENSE, null, null, false), false, null, null, null, null ) ੄޷হחё୓ܳࢤࢿೠ҃೷ పझ౟ܳਤ೧ ߸ࣻী੄޷হחчਸࢶ঱ೞѢա
  31. private val mockName = "user" private val mockPhoneNumber = "01012341234"

    private val mockRequestId = "testId" 5FTUDPEF੄ਬ૑ࠁࣻ੄য۰਑DBTF UserExpenseTransaction( "id", Amount(1000.0, ""), "title", null, testDate, TransactionProduct(“productid", "type", "name"), TransactionCategorySummary("", "", TransactionType.EXPENSE, null, null, false), false, null, null, null, null ) ੄޷হחё୓ܳࢤࢿೠ҃೷ పझ౟ܳਤ೧ ߸ࣻী੄޷হחчਸࢶ঱ೞѢա DBTF੄࢚ട੉੤അ
  32. 6OJUUFTU UserExpenseTransaction( "id", Amount(1000.0, ""), "title", null, testDate, TransactionProduct(“productid", "type",

    "name"), TransactionCategorySummary("", "", TransactionType.EXPENSE, null, null, false), false, null, null, null, null ) 1SFTFOUFSחঌ೙ਃبহחࣁࠗ੿ࠁܳঌѱغҊ పझ౟ܳਤ೧ࢲ೙ਃহחࣁࠗ੿ࠁܳࣁ౴೧ঠೣ fun onButtonClicked(transaction: UserExpenseTransaction) { … }
  33. 6OJUUFTU interface UserTransaction Class UserExpenseTransaction(…) : UserTransaction fun onButtonClicked(transaction: UserTransaction)

    { … } @Mock var userTransaction: UserTransaction or var userTransaction: UserTransaction = SimpleUserTransaction() ࣁࠗ੸ੋ੿ࠁহ੉೙ਃೠ݅ఀ੄੿ࠁ పझ౟௏٘੘ࢿदীب ࠛ೙ਃೠё୓ܳࢤࢿೞ૑ঋইبغҊ ূ౭౭о߸೧بపझ౟௏٘ࣻ੿੉೙ਃহ׮