Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

subroh_0508

February 17, 2021
Tweet

More Decks by subroh_0508

Other Decks in Technology

Transcript

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

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

     8BOUFEMZ  ΤϜεϦʔ OFUqJYUFDICMPHDPNOFUqJYBOESPJEBOEJPTTUVEJPBQQTLPUMJONVMUJQMBUGPSNEEEEE XXXXBOUFEMZDPNDPNQBOJFTXBOUFEMZQPTU@BSUJDMFT XXXNUFDICMPHFOUSZ    ৿ཏສ৅Λ,PUMJOͰ࣮૷Ͱ͖Δੈք ਱ʹݱ࣮ʹͳΔͷ͔ʜʂʁ ͍͘ΒϩδοΫΛڞ௨ԽͰ͖ͯ΋ ϏϧυεΫϦϓτ͕ࠇຐज़ͩͬͨΒҙຯͳ͍Μ͡Ό🥺 ,PUMJOΊͬͪΌΘ͔Δ ໠ऀ͡Όͳ͍ͱ࢖͍͜ͳͤͳ͍ͷͰ͸🥺 ˞ڞ௨ԽͰָΛ͢Δ ௒͑ΒΕͳ͍น ϏϧυͷͭΒΈ ɹ91MBUͰ͋Γ͕ͪͳ᠘
  3. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ 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 ,..Ͱ࡞੒ͨ͠ ॳظͷঢ়ଶ͔Βʜ
  4. ࣮૷ղઆ  㾎ϚϧνϞδϡʔϧ 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͔ΒࢀরՄೳʹʂ
  5. ࣮૷ղઆ  㾎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
  6. ࣮૷ղઆ  㾎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Λఆٛ
  7. ࣮૷ղઆ  㾎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ͷ֦ுϓϩύςΟΛࢀর
  8. ࣮૷ղઆ  㾎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Λ౉͢
  9. ࣮૷ղઆ  㾎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
  10. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,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
  11. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,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ϓϩύςΟʹ ϞδϡʔϧΛ౉ͯ͠஫ೖ
  12. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,PEFJOΛར༻͠ɺඞཁͳΠϯελϯεΛ஫ೖ J04ͷ৔߹ fun createViewModel( di: DI,

    ): IdolsViewModel = di.direct.instance<IdolsViewModel.Factory>().create() JPT.BJO/BUJWF%*LU 4XJGUͷΫϥεʹ%*"XBSFΛ্ख͘ܧঝͤ͞ΒΕͳ͍🥺 ˠ%*Ϟδϡʔϧ͔ΒΠϯελϯεΛ௚઀ҾͬுΔϝιουΛ࣮૷
  13. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,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
  14. ࣮૷ղઆ  㾎%*ίʔυڞ௨Խ  ,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*Ҏ֎ͷ෦෼͸ ΄΅શͯڞ௨Խ🎉
  15. ࠓޙͷల๬ɾ·ͱΊ 㾎ςετίʔυΛ.11Ͱॻ͖͍ͨ  "OESPJEJ04+4ͷϩδοΫΛςετίʔυͰνΣοΫͰ͖Δ ͸ͣ  㾎J04ͷSE1BSUZϥΠϒϥϦ΋.11Ͱѻ͍͍ͨ  $PDPB1PET͔Β,PUMJOͷੈքͰJ04༻ϥΠϒϥϦΛѻ͏͜ͱ͕Մೳ 

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