Slide 1

Slide 1 text

Compose Multiplatform for iOS ։ൃͰͿ͔ͭͬͨน XʢچTwitterʣ: https://twitter.com/nemo_855 GitHub: https://github.com/nemo-855 Qiita: https://qiita.com/nemo-855 Android ΤϯδχΞ 
 Ҫ্ ߊฏ / Ͷ΋ 1

Slide 2

Slide 2 text

Ҫ্ߊฏ @nemo_855 XͰϑΥϩʔ͓ئ͍͠·͢🙏 ● ZOZOTOWN։ൃຊ෦ ZOZOTOWN։ൃ1෦ Android2ϒϩοΫ ● Α͏΍͘VIVANTΛݟ࢝Ί·ͨ͠ 2

Slide 3

Slide 3 text

ݸਓͰiOSͱAndroidΞϓϦΛ࡞੒͠·ͨ͠ʂ 3 iOS൛ Android൛

Slide 4

Slide 4 text

Jetpack Composeͱಉ͡ॻ͖ຯͰ iOSΞϓϦΛ࡞ΕΔͷ͍͢͝ʂʂ 4 iOS൛ Android൛

Slide 5

Slide 5 text

͚ͩͲɺɺɺ 5 iOS൛ Android൛

Slide 6

Slide 6 text

🍎 ໨࣍ 6 🍋 TextFieldΛλοϓͨ࣌͠ͷڍಈ 🍋 Android Context໰୊ 🍋 ݖݶϦΫΤετͷਏΈ

Slide 7

Slide 7 text

🍍 TextFieldΛλοϓͨ࣌͠ͷڍಈ ͦͷ1 TextFieldΛλοϓͨ࣌͠ʹɺ ը໘֎ʹεΫϩʔϧͯ͠͠·͏ɻ 7

Slide 8

Slide 8 text

ComposeUiViewControllerͷconfigureҾ਺ʹ onFocusBehavior.DoNothingΛࢦఆ͢Ε͹ྑ͍ 🍍 TextFieldΛλοϓͨ࣌͠ͷڍಈ ͦͷ2 fun PostViewController( viewModel: PostViewModel, closePage: () -> Unit, ): UIViewController { return ComposeUIViewController( configure = { onFocusBehavior = OnFocusBehavior.DoNothing }, ) { PostPage( context = IosKmpContext, viewModel = viewModel, closePage = closePage ) } } 8

Slide 9

Slide 9 text

໌֬ʹ͜ͷόάʹ͍ͭͯͷ GitHub issue͕ແ͔ͬͨ ↓ #3128, #3537, #3856ͳͲͷ ؔ࿈͢Δissue͔Βਪଌͯ͠ࢼߦࡨޡ͢ Δ͔͠ͳ͍ 🍍 TextFieldΛλοϓͨ࣌͠ͷڍಈ ͦͷ3 9

Slide 10

Slide 10 text

🍐 Android Context໰୊ ͦͷ1 Compose Multiplatformͷڞ༗ϞδϡʔϧͰɺ ը૾Λѻ͏ࡍʹͲ͏ͯ͠΋ Android Context͕ඞཁʹͳΔ 10

Slide 11

Slide 11 text

🍐 Android Context໰୊ ͦͷ2 11 actual fun Uri.getImageFlow(context: KmpContext): Flow { return flow { val bitmap = this.getBitmapOrNull(context.contentResolver) if (bitmap != null) { emit(bitmap) } } } private fun Uri.getBitmapOrNull(contentResolver: ContentResolver): ImageBitmap? { return kotlin.runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val source = ImageDecoder.createSource(contentResolver, this) ImageDecoder.decodeBitmap(source) } else { MediaStore.Images.Media.getBitmap(contentResolver, this) } }.getOrNull()?.asImageBitmap() }

Slide 12

Slide 12 text

🍐 Android Context໰୊ ͦͷ3 // commonMain expect abstract class KmpContext // androidMain actual typealias KmpContext = android.content.Context // iosMain actual abstract class KmpContext object IosContext : KmpContext() kotlinlang SlackνϟϯωϧͰ ঺հ͞Ε͍ͯͨख๏ ӈਤͷΑ͏ͳKmpContextΛ࡞੒͢Δ 12

Slide 13

Slide 13 text

🍐 Android Context໰୊ ͦͷ3 // commonMain expect abstract class KmpContext // androidMain actual typealias KmpContext = android.content.Context // iosMain actual abstract class KmpContext object IosContext : KmpContext() AndroidଆͰ͸AndroidͷContextɺ iOSଆͰ͸μϛʔͷContextΛ࢖༻͢Δ 13

Slide 14

Slide 14 text

🍐 Android Context໰୊ ͦͷ4 Androidଆͷࣄ৘Λڞ௨Ϟδϡʔϧʹແཧ໼ཧ ࣋ͪࠐΜͰ͍ΔͷͰෳࡶͳॲཧʹͳ͍ͬͯΔ Compose MultiplatformಠࣗͷਏΈ 14

Slide 15

Slide 15 text

ը૾ͷΞΫηεͷࡍʹͦΕͧΕͷOSͰݖݶ ΛϦΫΤετ͠ͳ͍ͱ͍͚ͳ͍͕ɺ ͦΕΒͷॲཧΛڞ௨Խ͢Δ͜ͱ͕೉͍͠ ↓ ίϯϙʔωϯτͷߏ੒Λ޻෉͢Δ 🥭 ݖݶϦΫΤετͷਏΈ ͦͷ1 15

Slide 16

Slide 16 text

🥭 ݖݶϦΫΤετͷਏΈ ͦͷ3 val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { // ͜͜ͰݖݶͷεςʔλεʹԠͯ͡෼ذॲཧ } val permission = if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) { READ_EXTERNAL_STORAGE } else { READ_MEDIA_IMAGES } launcher.launch(permission) PHPhotoLibrary.requestAuthorization { status in // ίίͰݖݶͷεςʔλεʹԠͯ͡෼ذॲཧ } Android iOS 16

Slide 17

Slide 17 text

🥭 ݖݶϦΫΤετͷਏΈ ͦͷ3 val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { // ͜͜ͰݖݶͷεςʔλεʹԠͯ͡෼ذॲཧ } val permission = if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) { READ_EXTERNAL_STORAGE } else { READ_MEDIA_IMAGES } launcher.launch(permission) PHPhotoLibrary.requestAuthorization { status in // ίίͰݖݶͷεςʔλεʹԠͯ͡෼ذॲཧ } Android iOS ݖݶϦΫΤετͷॲཧ & ݖݶεςʔλεͷछྨ͕ҟͳΔͷͰɺ expect / actualͰ΋ڞ௨Խ͕͠ਏ͍ 17

Slide 18

Slide 18 text

commonMain 🥭 ݖݶϦΫΤετͷਏΈ ͦͷ4 androidMain iosApp Template Page View View ը໘ભҠॲཧ ʢCompose Navigationʣ View ը໘ભҠॲཧ (SwiftUI) 18

Slide 19

Slide 19 text

commonMain 🥭 ݖݶϦΫΤετͷਏΈ ͦͷ5 androidMain iosApp Template Page View View ը໘ભҠॲཧ ʢCompose Navigationʣ View ը໘ભҠॲཧ (SwiftUI) ɾը໘ͷঢ়ଶͷ؅ཧ ɾViewModelͷอ࣋ expect / actualͰڞ௨ԽͰ͖ͳ͍ॲཧ ྫʣ ݖݶϦΫΤετॲཧ 19

Slide 20

Slide 20 text

🥭 ݖݶϦΫΤετͷਏΈ ͦͷ6 @Composable fun PostView(closeView: () -> Unit) { // ViewModelऔಘॲཧ౳ val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) { // no-op } LaunchedEffect(Unit) { if (!PermissionChecker(context).isGallaryPermissionGranted()) { PermissionRequester().requestGallaryPermission(launcher) } } PostPage( context = context, viewModel = viewModel, closePage = closeView, ) } 20

Slide 21

Slide 21 text

🥭 ݖݶϦΫΤετͷਏΈ ͦͷ7 private struct PostView: UIViewControllerRepresentable { // লུ: ϓϩύςΟͷॳظԽॲཧ func makeUIViewController(context: Context) -> UIViewController { if (!isGallaryPermissionGranted()) { requestGallaryPermission(completion: { isGranted in // no-op }) } return MainKt.PostViewController( viewModel: viewModel, closePage: closeView ) } // লུ: updateUIViewController, dismantleUIViewController } 21

Slide 22

Slide 22 text

ࣗ෼͕Ϳ͔ͭͬͨนΛ঺հ͢Δ͜ͱͰɺ ผͷਓ͕ಉ͡಺༰ͰࠔΒͳ͍Α͏ʹͳͬͯཉ͍͠ʂʂ 22

Slide 23

Slide 23 text

🍇 ࢀߟ ✓ https://github.com/JetBrains/compose-multiplatform/issues/3128 ✓ https://github.com/JetBrains/compose-multiplatform/issues/3537 ✓ https://github.com/JetBrains/compose-multiplatform/issues/3856 ✓ https://kotlinlang.org/community/ 23

Slide 24

Slide 24 text

24