Save 37% off PRO during our Black Friday Sale! »

Kotlin MPPでUIロジックまで共通化して楽をしたい

7c3b3366947123ba6772698b09edf4e2?s=47 subroh_0508
February 17, 2021

Kotlin MPPでUIロジックまで共通化して楽をしたい

集まれKotlin好き!Kotlin愛好会 vol.27の談義資料です。

https://love-kotlin.connpass.com/event/202929/

サンプルアプリ: https://github.com/subroh0508/KMM-Sample

7c3b3366947123ba6772698b09edf4e2?s=128

subroh_0508

February 17, 2021
Tweet

Transcript

  1. ,PUMJO.11Ͱ 6*ϩδοΫ·Ͱڞ௨Խͯ͠ ָΛ͍ͨ͠ ,PUMJOѪ޷ձ ʹ͜͠Γ͞ͿΖʙ!TVCSPI@

  2. "CPVU.F ʹ͜͠Γ͞ͿΖʙ ✦౦ژ౎ɾҏ౾େౡग़਎ ✦גࣜձࣾ#&"35"*- ‣ "OESPJEΤϯδχΞ ,PUMJO+BWB ˑ ‣ 8FCΤϯδχΞ

    3BJMT3FBDU
  3. ͸͡Ίʹʜ ,PUMJO.VMUJQMBUGPSN ۀ຿PSझຯͰ৮͍ͬͯΔਓʔʂ🙌

  4. ͸͡Ίʹʜ 㾎͜͜࠷ۙ੝Γ্͕Γͭͭ͋Δ,PUMJO.VMUJQMBUGPSN  ೥݄ɺ,PUMJO.VMUJQMBUGPSN.PCJMFͷЋ൛ϦϦʔε  ϞόΠϧ޲͚ϓϩδΣΫτͷ਽ܗΛࣗಈੜ੒  ຊ൪؀ڥͰͷ࠾༻ࣄྫ  /FUqJY

     8BOUFEMZ  ΤϜεϦʔ OFUqJYUFDICMPHDPNOFUqJYBOESPJEBOEJPTTUVEJPBQQTLPUMJONVMUJQMBUGPSNEEEEE XXXXBOUFEMZDPNDPNQBOJFTXBOUFEMZQPTU@BSUJDMFT XXXNUFDICMPHFOUSZ    ৿ཏສ৅Λ,PUMJOͰ࣮૷Ͱ͖Δੈք ਱ʹݱ࣮ʹͳΔͷ͔ʜʂʁ
  5. ͸͡ΊͷҰาΛ્Ή৺ͷ੠ 㾎͜͜࠷ۙ੝Γ্͕Γͭͭ͋Δ,PUMJO.VMUJQMBUGPSN  ೥݄ɺ,PUMJO.VMUJQMBUGPSN.PCJMFͷЋ൛ϦϦʔε  ϞόΠϧ޲͚ϓϩδΣΫτͷ਽ܗΛࣗಈੜ੒  ຊ൪؀ڥͰͷ࠾༻ࣄྫ  /FUqJY

     8BOUFEMZ  ΤϜεϦʔ OFUqJYUFDICMPHDPNOFUqJYBOESPJEBOEJPTTUVEJPBQQTLPUMJONVMUJQMBUGPSNEEEEE XXXXBOUFEMZDPNDPNQBOJFTXBOUFEMZQPTU@BSUJDMFT XXXNUFDICMPHFOUSZ    ৿ཏສ৅Λ,PUMJOͰ࣮૷Ͱ͖Δੈք ਱ʹݱ࣮ʹͳΔͷ͔ʜʂʁ ͍͘ΒϩδοΫΛڞ௨ԽͰ͖ͯ΋ ϏϧυεΫϦϓτ͕ࠇຐज़ͩͬͨΒҙຯͳ͍Μ͡Ό🥺 ,PUMJOΊͬͪΌΘ͔Δ ໠ऀ͡Όͳ͍ͱ࢖͍͜ͳͤͳ͍ͷͰ͸🥺 ˞ڞ௨ԽͰָΛ͢Δ ௒͑ΒΕͳ͍น ϏϧυͷͭΒΈ ɹ91MBUͰ͋Γ͕ͪͳ᠘
  6. ࿩͢͜ͱ ˑ,PUMJO.11ͰΞϓϦΛαΫͬͱ࡞ͬͯײͨ͜͡ͱ 㾎Ϟμϯͳߏ੒ͷΞϓϦΛͲͷఔ౓ͷ࿑ྗͰ࣮૷Ͱ͖Δ͔ 㾎6*ϩδοΫ·Ͱڞ௨Խͯ͠ྑ͔ͬͨ͜ͱɺͭΒ͔ͬͨ͜ͱ 㾎ࠓޙͷల๬

  7. ,PUMJO.11ͷҒେͳઌߦࣄྫ 㾎%SPJE,BJHJެࣜΞϓϦ 💪೔ຊޠݍͷ։ൃऀ͕࠷΋ΞΫηε͠΍͘͢ɺ͔ͭ৘ใྔͷଟ͍ΞϓϦ 💪$PSPVUJOFTʹΑΔඇಉظॲཧ ϚϧνϞδϡʔϧ %*CZ%BHHFS ͔͠͠J04ଆͰ͸ʜ 🥺TVTQFOE͕࢖͑ͳ͍ˠίʔϧόοΫͰϒϦοδ 🥺ෳ਺ϞδϡʔϧͷࢀরʹIBDL͕ඞཁ (JU)VC%SPJE,BJHJDPOGFSFODFBQQ

    ·ͩ·ͩػೳෆ଍ͩͬͨ,PUMJOͰ ΊͪΌͪ͘Όؤு͍ͬͯΔ௒ҒେͳΞϓϦ
  8. ,PUMJOͱ,..ͷొ৔ 㾎,PUMJOͷ.11త໨ۄ 🎉,PUMJO/BUJWFͰͷTVTQFOEؔ਺ͷαϙʔτ 🎉.11޲͚ͷެࣜυΩϡϝϯτ͕΍ͨΒ૿͑ͨ 㾎,..ͷొ৔ 🥰΢ΟβʔυܗࣜͰ"OESPJEJ04྆ରԠͷ)FMMP 8PSME͕࡞ΕΔʂ 🥰ϛχϚϜͳJ04޲͚ͷϏϧυεΫϦϓτ͕࠷ॳ͔Β༻ҙ͞Ε͍ͯΔʂ ࣌఺ ࣌఺

    ,PUMJOͰͷػೳ௥Ճ ,..Ͱͷॳಈαϙʔτ ܥͷ࣌୅͔Β࣮֬ʹ৮Γ΍͘͢ͳ͍ͬͯΔʂ
  9. ࣮ࡍʹ࡞ͬͨϞϊ 㾎ΞΠυϧϚελʔʹొ৔͢ΔΞΠυϧΛݕࡧ͢ΔΞϓϦ  $PSPVUJOFTʹΑΔඇಉظॲཧ ϚϧνϞδϡʔϧ  6*ϩδοΫ 7JFX.PEFM ·Ͱڞ௨Խ 

    %*༻ίʔυ΋΄΅ڞ௨Խ
  10. ࣮૷ղઆ  㾎$PSPVUJOFTʹΑΔඇಉظॲཧ  LPUMJOYDPSPVUJOFT͕.11ʹରԠ  ϚϧνεϨουରԠͷBSUJGBDUΛར༻  γϯϓϧͳ֎෦"1*ͱͷ΍ΓऔΓͳΒ໰୊ͳ͘ಈ࡞ ͜Ε

    ˞4UBCMF൛ͱͷࠩ͸ׂͱ͋ΔͷͰෳࡶͳ͜ͱΛ ͢Δͱ૝ఆ֎ͷڍಈʹͳΔ͔΋ʜ
  11. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ  ԼهͷΑ͏ͳϞδϡʔϧߏ੒ͷ৔߹ BQJˠ)UUQ$MJFOUͷఆٛ DPNQPOFOUTˠ%*ؔ࿈ͷϝιουɾΫϥε J04޲͚BSUJGBDUੜ੒ EBUBJOGSBˠϨϙδτϦͷఆٛ EBUBNPEFMˠΤϯςΟςΟɾ஋ΦϒδΣΫτͷఆٛ

    QSFTFOUBUJPOˠ6*ϩδοΫͷఆٛ
  12. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ kotlin { android() ios { binaries {

    framework { baseName = "shared" } } } sourceSets { val commonMain by getting { dependencies { implementation(project(":shared:data:model")) implementation(project(":shared:presentation")) implementation(project(":shared:api")) implementation(project(":shared:data:infra")) ... } DPNQPOFOUTCVJMEHSBEMFLUT ,..Ͱ࡞੒ͨ͠ ॳظͷঢ়ଶ͔Βʜ
  13. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ kotlin { android() ios { binaries {

    framework { baseName = "shared" export(project(":shared:data:model")) export(project(":shared:presentation")) } } } sourceSets { val commonMain by getting { dependencies { api(project(":shared:data:model")) api(project(":shared:presentation")) implementation(project(":shared:api")) implementation(project(":shared:data:infra")) ... } J04޲͚ʹެ։͍ͨ͠ ϞδϡʔϧΛFYQPSU DPNNPO.BJO಺ͷґଘؔ܎Λ JNQMFNFOUBUJPOˠBQJʹमਖ਼ DPNQPOFOUTCVJMEHSBEMFLUT ˒EBUBNPEFMͱQSFTFOUBUJPO಺ͷ Ϋϥεɾϝιου͕J04͔ΒࢀরՄೳʹʂ
  14. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ ҼΈʹܥͷ࣌୅ͷ ϚϧνϞδϡʔϧରԠ͸ʜ SFGRJJUBDPNUBLBIJSPNJUFNTEGCGDBBF ͱͯ΋ͭΒ͍🥺 ˒ϚϧνϞδϡʔϧରԠ͸ܥ͔Β େ෯ʹָʹͳͬͨ🎉

  15. ࣮૷ղઆ  㾎6*ϩδοΫڞ௨Խ  ""$ͷ7JFX.PEFMΛDPNNPO.BJOͰѻ͑ΔΑ͏ʹίʔυΛ௥Ճ expect abstract class ViewModel constructor(

    coroutineScope: CoroutineScope? = null, ) { val viewModelScope: CoroutineScope } DPNNPO.BJO7JFX.PEFMLU import androidx.lifecycle.ViewModel as AndroidViewModel import androidx.lifecycle.viewModelScope as androidViewModelScope actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) : AndroidViewModel() { actual val viewModelScope = coroutineScope ?: androidViewModelScope } BOESPJE.BJO"OESPJE7JFX.PEFMLU actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) { actual val viewModelScope = coroutineScope ?: MainScope() } JPT.BJO/BUJWF7JFX.PEFMLU
  16. ࣮૷ղઆ  㾎6*ϩδοΫڞ௨Խ  ""$ͷ7JFX.PEFMΛDPNNPO.BJOͰѻ͑ΔΑ͏ʹίʔυΛ௥Ճ expect abstract class ViewModel constructor(

    coroutineScope: CoroutineScope? = null, ) { val viewModelScope: CoroutineScope } DPNNPO.BJO7JFX.PEFMLU import androidx.lifecycle.ViewModel as AndroidViewModel import androidx.lifecycle.viewModelScope as androidViewModelScope actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) : AndroidViewModel() { actual val viewModelScope = coroutineScope ?: androidViewModelScope } BOESPJE.BJO"OESPJE7JFX.PEFMLU actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) { actual val viewModelScope = coroutineScope ?: MainScope() } JPT.BJO/BUJWF7JFX.PEFMLU DPNNPO.BJOʹ FYQFDUम০ࢠΛ͚ͭͯ 7JFX.PEFMΛఆٛ
  17. ࣮૷ղઆ  㾎6*ϩδοΫڞ௨Խ  ""$ͷ7JFX.PEFMΛDPNNPO.BJOͰѻ͑ΔΑ͏ʹίʔυΛ௥Ճ expect abstract class ViewModel constructor(

    coroutineScope: CoroutineScope? = null, ) { val viewModelScope: CoroutineScope } DPNNPO.BJO7JFX.PEFMLU import androidx.lifecycle.ViewModel as AndroidViewModel import androidx.lifecycle.viewModelScope as androidViewModelScope actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) : AndroidViewModel() { actual val viewModelScope = coroutineScope ?: androidViewModelScope } BOESPJE.BJO"OESPJE7JFX.PEFMLU actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) { actual val viewModelScope = coroutineScope ?: MainScope() } JPT.BJO/BUJWF7JFX.PEFMLU ""$ͷ7JFX.PEFMΛܧঝ WJFX.PEFM4DPQF΋ +FUQBDLͷ֦ுϓϩύςΟΛࢀর
  18. ࣮૷ղઆ  㾎6*ϩδοΫڞ௨Խ  ""$ͷ7JFX.PEFMΛDPNNPO.BJOͰѻ͑ΔΑ͏ʹίʔυΛ௥Ճ expect abstract class ViewModel constructor(

    coroutineScope: CoroutineScope? = null, ) { val viewModelScope: CoroutineScope } DPNNPO.BJO7JFX.PEFMLU import androidx.lifecycle.ViewModel as AndroidViewModel import androidx.lifecycle.viewModelScope as androidViewModelScope actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) : AndroidViewModel() { actual val viewModelScope = coroutineScope ?: androidViewModelScope } BOESPJE.BJO"OESPJE7JFX.PEFMLU actual abstract class ViewModel actual constructor( coroutineScope: CoroutineScope? ) { actual val viewModelScope = coroutineScope ?: MainScope() } JPT.BJO/BUJWF7JFX.PEFMLU J04ଆ͸Կ΋ܧঝ͠ͳ͍ WJFX.PEFM4DPQF͸ ͱΓ͋͑ͣ .BJO4DPQFΛ౉͢
  19. ࣮૷ղઆ  㾎6*ϩδοΫڞ௨Խ class IdolsViewModel( private val repository: IdolsRepository, )

    : ViewModel() { private val idolsLoadState: MutableStateFlow<LoadState> = MutableStateFlow(LoadState.Initialize) private val queryState: MutableStateFlow<String> = MutableStateFlow("") val uiModel: StateFlow<IdolsUiModel> get() = combine( idolsLoadState, queryState, ) { idolsLoadState, _ -> IdolsUiModel(idolsLoadState) }.distinctUntilChanged().stateIn(viewModelScope, SharingStarted.Eagerly, IdolsUiModel()) fun search(query: String? = null) { val job = viewModelScope.launch(start = CoroutineStart.LAZY) { runCatching { withContext(Dispatchers.Default) { repository.search(query) } } .onSuccess { idolsLoadState.value = LoadState.Loaded(it) } .onFailure { idolsLoadState.value = LoadState.Error(it) } } idolsLoadState.value = LoadState.Loading(job) job.start() } } ˒"OESPJEJ04ͷࠩΛҙࣝͤͣ ɹ6*ϩδοΫͷ࣮૷͕Մೳʹ🎉 ໊લݕࡧػೳΛ࣮૷ͨ͠ 7JFX.PEFM DPNNPO.BJO*EPMT7JFX.PEFMLU
  20. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOͱ,PJO Ћ൛ ͕.11ʹରԠ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ "OESPJEͷ৔߹

    class MainActivity : AppCompatActivity(), DIAware { override val di: DI = IdolsViewModelDI(Main.Module) private val viewModel: IdolsViewModel by viewModels { di.direct.instance<IdolsViewModel.Factory>() } ... .BJO"DUJWJUZLU
  21. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOͱ,PJO Ћ൛ ͕.11ʹରԠ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ "OESPJEͷ৔߹

    class MainActivity : AppCompatActivity(), DIAware { override val di: DI = IdolsViewModelDI(Main.Module) private val viewModel: IdolsViewModel by viewModels { di.direct.instance<IdolsViewModel.Factory>() } ... .BJO"DUJWJUZLU %*"XBSFΛܧঝ EJϓϩύςΟʹ ϞδϡʔϧΛ౉ͯ͠஫ೖ
  22. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ J04ͷ৔߹ fun createViewModel( di: DI,

    ): IdolsViewModel = di.direct.instance<IdolsViewModel.Factory>().create() JPT.BJO/BUJWF%*LU 4XJGUͷΫϥεʹ%*"XBSFΛ্ख͘ܧঝͤ͞ΒΕͳ͍🥺 ˠ%*Ϟδϡʔϧ͔ΒΠϯελϯεΛ௚઀ҾͬுΔϝιουΛ࣮૷
  23. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ J04ͷ৔߹ class MainViewController: UIViewController, UISearchBarDelegate

    { private let di = IdolsViewModelDIKt.IdolsViewModelDI(modules: [Main().Module]) override func viewDidLoad() { super.viewDidLoad() viewModel = NativeDIKt.createViewModel(di: di) } ... .BJO7JFX$POUSPMMFSTXJGU
  24. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ J04ͷ৔߹ class MainViewController: UIViewController, UISearchBarDelegate

    { private let di = IdolsViewModelDIKt.IdolsViewModelDI(modules: [Main().Module]) override func viewDidLoad() { super.viewDidLoad() viewModel = NativeDIKt.createViewModel(di: di) } ... .BJO7JFX$POUSPMMFSTXJGU %*ϞδϡʔϧΛ6*7JFX$POUSPMMFS಺Ͱੜ੒͠ɺ ࣮૷ͨ͠αϙʔτϝιουͰ7JFX.PEFMΛऔಘ ˒6*Ҏ֎ͷ෦෼͸ ΄΅શͯڞ௨Խ🎉
  25. 6*ϩδοΫ·Ͱڞ௨Խͯ͠ײͨ͜͡ͱ 㾎༧૝Ҏ্ʹϞμϯͳߏ੒ͷΞϓϦΛָʹ࣮૷Ͱ͖ͨ  ඇಉظॲཧɺϏϧυपΓͷͭΒ͍෦෼͕େ͖͘վળ࣮͠༻తʹͳͬͨ  "OESPJEJ04Λݸผ࣮૷͢ΔΑΓ΋ѹ౗తʹ଎࣮͘૷Ͱ͖ͨ  ͭΒ͍෦෼ΑΓ΋ڞ௨ԽͷԸܙ͕େ͖͍ঢ়ଶʹ͔ͳΓ͍͍ۙͮͯΔ  .11ͷ੝Γ্͕Γʹ߹ΘͤͯSE1BSUZϥΠϒϥϦ΋੒௕ͭͭ͋͠Δ

    Ұ෦ͷ໠ऀ͕௒ؤுͬͯѻ͏ஈ֊͔Βɺ ৽͍ٕ͠ज़ͱָͯ͠͠Έͳ͕Βѻ͑Δஈ֊ʹͳΓͭͭ͋Δ ؾ͕͢Δ  ˞4XJGUྺ਺೔ͷݸਓͷײ૝Ͱ͢
  26. ͭΒ͔ͬͨ͜ͱ 㾎9DPEFɾ4XJGUɾ6*,JUͳΜ΋Θ͔ΒΜ😇  αϯϓϧΞϓϦͷ࣮૷Ͱ࠷΋࣌ؒΛফඅͨ͠ͷ͕J04ͷ6*࣮૷  4XJGUͱ,PUMJO͸ࣅ͍ͯΔ͕ɺ"OESPJE4UVEJPͱ9DPEF͸׬શʹผ෺ 㾎,PUMJOͱ4XJGUͷܕͷରԠؔ܎ʹ᠘͕͋Δ  ,PUMJOͷ-JTUˠ4XJGUͷ"SSBZ 

    ,PUMJOͷ"SSBZˠ4XJGUͰ͸,PUMJO"SSBZ🤯 ७ਮͳ6*࣮૷ʹ͚ͩूதͰ͖ͨͷ͸ .11͔ͩΒͦ͜ͷར఺͔΋ʜ🤔
  27. ࠓޙͷల๬ɾ·ͱΊ 㾎ςετίʔυΛ.11Ͱॻ͖͍ͨ  "OESPJEJ04+4ͷϩδοΫΛςετίʔυͰνΣοΫͰ͖Δ ͸ͣ  㾎J04ͷSE1BSUZϥΠϒϥϦ΋.11Ͱѻ͍͍ͨ  $PDPB1PET͔Β,PUMJOͷੈքͰJ04༻ϥΠϒϥϦΛѻ͏͜ͱ͕Մೳ 

    'JSFCBTF౳ͷϥΠϒϥϦΛ.11Ͱѻ͑ͨΒઈରָ͍͠ ϋʔυϧ͕೔ʹ೔ʹԼ͕Γͭͭ͋Δ,PUMJO.11Ͱͷ91MBU։ൃ Έͳ͞Μ΋ָָ͘͠Λͯ͠Έ·ͤΜ͔ʜʁ🥰 5IBOLZPVGPSMJTUFOJOH