11/19 (토) DevFest 2022 - GDG Songdo, Incheon & GDSC Inha, TUKorea 행사에서 발표한 자료입니다. https://festa.io/events/2758
아래 오픈소스 라이브러리에 대한 내용입니다. https://github.com/fornewid/naver-map-compose
Songdo, IncheonNAVER Mapin Jetpack ComposeSungyong An Android GDE, NAVER WEBTOON
View Slide
Sungyong AnNAVER WEBTOONAndroid GDE@fornewidLink: https://github.com/fornewid
Sungyong AnNAVER WEBTOONAndroid GDE@fornewidLink: https://github.com/fornewidSlide Templateठۄ٘ ೞױী ݂о णפ.
Code TemplateView Composeஏ ࢚ױী যڃ ٘ੋ ইਸ दפ.Compose ٘݅ ઝஏী ֣࢝ ߄ܳ दפ.
Link: https://developersonair.withgoogle.com/events/composecamp_22krJetpack Compose ण ਃೠ ࠙ Compose Camp ೯ࢎী ଵৈ೧ࠁࣁਃ!
Link: https://developer.android.com/develop/ui/views/layout/declaring-layoutandroid:layout_height="match_parent"android:orientation="vertical" >android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello, I am a TextView" />android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello, I am a Button" />View Systemഅসীࢲח View Systemਵ۽ ѐߊೞח Ѫ ࣼೡ Ѫ эणפ. উ٘۽٘ ѐߊਸ ೡ ٸب ࠁా Layout XMLࠗఠ दೞભ.
Link: https://developer.android.com/jetpack/composeJetpack Compose (ModernAndroidDevelopment)ਃ્ীח Jetpack Composeо ݆ ࣗѐغҊ ҳਃ.࢜۽ ࣗध ߈ Composeੌ ب۽ ҙबਸ ߉Ҋ णפ.
Link: https://developer.android.com/jetpack/compose/interop/migration-strategyMigrate from View to Compose🤔Ӓ۞ࠁפ ݆ ٜ࠙ Viewܳ Compose۽ ജೞҊ Ѣա, ജਸ Ҋ۰ೞҊ ਸѢۄ ࢤпפ.
Toy ProjectLink: https://github.com/Moop-App/Moop-Androidয়ט അসীࢲ ਊೠ ղਊ ইפҊ,ష ۽ંী ೠ ղਊੑפ.
Link: https://github.com/navermaps/android-map-sdkOfficial SDKbased on View🤔֎ߡب SDKח View Systemਵ۽ ҳഅغয णפ. Ӓېࢲ Ѧ ஹನૉীࢲ যڌѱ ॶ ࣻ ਸ Ҋ೮ભ.
How to use in Jetpack Compose?-(1) ݽٚ ٘ܳ Compose۽ द ࢿೠ-(2) ೨ब ٘ܳ ܻ࠙ೞৈ Composeীࢲ ഝਊೠ-(3) ࢚ഐ ਊࢿ APIܳ ࢎਊೞৈ ӝઓ ٘ܳ ېೝೠݢ ݽٚ ٘ܳ Compose۽ ৮ ࢜۽ ҳഅೞח ߑߨ णפ.ਵ۽ ೨ब ٘ܳ ܻ࠙ೞৈ, ੌࠗ࠙ ࠗ࠙݅ Compose۽ ࢿೞח ߑߨ णפ. ݄݃ਵ۽ ࢚ഐਊࢿ APIܳ ਊೞৈ, ӝઓ ٘ܳ ېೝೞח ߑߨب णפ.
How to use in Jetpack Compose?-(1) ݽٚ ٘ܳ Compose۽ द ࢿೠ (X)-(2) ೨ब ٘ܳ ܻ࠙ೞৈ Composeীࢲ ഝਊೠ (X)-(3) ࢚ഐ ਊࢿ APIܳ ࢎਊೞৈ ӝઓ ٘ܳ ېೝೠ ✅NAVER Map SDK != Open Source Projectইऔѱب ֎ߡب SDKח য়ࣗझо ইפۄࢲ ࣻೡ ߑߨ হणפ. Ӓېࢲ ݄݃ ߑߨੋ ӝઓ ٘ܳ ېೝೞח ߑधਵ۽ ೯೮णפ.
Link: https://github.com/Moop-App/Moop-Android/pull/120(1) Migrate using Compose APIীח Compose APIܳ ਊ೧ࢲ ష ۽ંী ࢤ۽ ٸ۰߅ח(?) সਸ ೮णפ.
Link: https://github.com/fornewid/naver-map-compose(2) For Jetpack ComposeUnofficial wrapper libraryӒ ীח ખ ؊ ҕਊചػ ਬૉாझী ݏ Compose wrapper ۄ࠳۞ܻܳ ٜ݅णפ.
Jetpack Composeীࢲ ֎ߡب SDK ࢎਊೞӝݾࠂೠ Widgetਸ Composeীࢲ ഝਊೞח ߑߨ
Toy ProjectUseCase ҳഅೞӝLink: https://github.com/Moop-App/Moop-Android/pull/120ݢ ష ۽ંী ҳഅೠ 5о ਬૉாझܳ ಝࠁѷणפ.
Use Case 1:ب ࠁৈӝ
MapViewandroid:id="@+id/map_view"android:layout_width="match_parent"android:layout_height="match_parent" />View Stystemীࢲח ۨইਓ XMLী MapViewܳ ࢶೞҊ
MapViewclass MapViewActivity : AppCompatActivity() {private var mapView: MapView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_map_view)mapView = findViewById(R.id.map_view)}}Activityীࢲ ࢸ೧ݶ ؾפ
MapViewclass MapComposeActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent { ... }}}Link: https://developer.android.com/jetpack/compose/interop/interop-apis#compose-in-viewsComposeীࢲח Layout XML नsetContentী Composableਸ ׳ೞݶ ؾפ
MapViewval context = LocalContext.currentAndroidView(factory = { context -> MapView(context) })Link: https://developer.android.com/jetpack/compose/interop/interop-apis#views-in-composeۧѱ AndroidViewۄח APIо ઁҕغҊ যࢲ factoryীࢲ MapViewܳ ࢤࢿೞݶ ؾפ
Link: https://developer.android.com/guide/components/activities/activity-lifecycleLifecycleӒۧ݅ Lifecycleী ೧ ઑӘ ؊ ܞঠೞח ࠗ࠙ णפ
MapView Lifecycleoverride fun onCreate(...) {super.onCreate(...)mapView?.onCreate(...)}override fun onStart() {super.onStart()mapView?.onStart()}override fun onResume() {super.onResume()mapView?.onResume()}override fun onPause() {super.onPause()mapView?.onPause()}override fun onStop() {super.onStop()mapView?.onStop()}override fun onDestroy() {super.onDestroy()mapView?.onDestroy()}🤔View Systemীࢲח ۠ ഋక۽ ഐೞҊ ਸ ѩפ
MapView Lifecycleval context = LocalContext.currentAndroidView(factory = { context -> MapView(context) })द ٘۽ جইоࠁѷणפ
MapView Lifecycleval context = LocalContext.currentval mapView = remember { MapView(context) }AndroidView(factory = { mapView })Link: https://developer.android.com/jetpack/compose/performance/bestpractices#use-rememberMapView ೣࣻܳ ഐ೧ঠ ೞפө,ݢ remember ೣࣻܳ ਊ೧ࢲ MapViewܳ ܻ࠙೧յ ѩפ
Link: https://developer.android.com/jetpack/compose/lifecyclerememberRecompositionীࢲ ࢚కܳ ਬೠ.рр ۧѱ Compose APIܳ ࢸݺೞח ठۄ٘о णפ
MapView Lifecycleval context = LocalContext.currentval mapView = remember(context){ MapView(context) } // (X)AndroidView(factory = { mapView })Link: https://developer.android.com/jetpack/compose/performance/bestpractices#use-remember ٸ remember ೣࣻী keyܳ ֍ਵݶ,keyо ߸҃ؼ ٸ݃ MapViewܳ द ࢤࢿೞ۽ ೧ঠ פ
MapView Lifecycleval context = LocalContext.currentval mapView = remember { MapView(context) }AndroidView(factory = { mapView })Link: https://developer.android.com/jetpack/compose/performance/bestpractices#use-rememberৈӝࢲח MapViewо Compositionীࢲ݅ ೠߣ ࢤࢿغب۾ keyܳ ֍ ঋणפ
MapView Lifecycleval mapView = rememberMapViewWithLifecycle()AndroidView(factory = { mapView })@Composablefun rememberMapViewWithLifecycle(): MapView {val context = LocalContext.currentval mapView = remember { MapView(context) }...return mapView}MapViewܳ ܻ࠙೮ਵפ, ઁח Lifecycleਸ ܻ೧ࠅөਃ?
MapView Lifecycleprivate fun getMapLifecycleObserver(mapView: MapView,savedInstanceState: Bundle?) = LifecycleEventObserver { _, event ->when (event) {Lifecycle.Event.ON_CREATE -> mapView.onCreate(savedInstanceState)Lifecycle.Event.ON_START -> mapView.onStart()Lifecycle.Event.ON_RESUME -> mapView.onResume()Lifecycle.Event.ON_PAUSE -> mapView.onPause()Lifecycle.Event.ON_STOP -> mapView.onStop()Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()else -> throw IllegalStateException()}}LifecycleEventObserverܳ ਊ೧ࢲпп ߮݃ MapView ೣࣻܳ ഐ೧સפ
MapView Lifecyclefun rememberMapViewWithLifecycle(): MapView {...val lifecycle = LocalLifecycleOwner.current.lifecycleDisposableEffect(lifecycle, mapView) {val lifecycleObserver = getMapLifecycleObserver(mapView, null)lifecycle.addObserver(lifecycleObserver)onDispose {lifecycle.removeObserver(lifecycleObserver)}}return mapView}ӒܻҊ Observerܳ Lifecycleী োѾ/೧ઁ೧ח ٘ܳ ࢿೞݶ ؾפ
MapView Lifecyclefun rememberMapViewWithLifecycle(): MapView {...val lifecycle = LocalLifecycleOwner.current.lifecycleDisposableEffect(lifecycle, mapView) {val lifecycleObserver = getMapLifecycleObserver(mapView, null)lifecycle.addObserver(lifecycleObserver)onDispose {lifecycle.removeObserver(lifecycleObserver)}}return mapView}Link: https://developer.android.com/jetpack/compose/side-effects#disposableeffect ٸ, DisposableEffect৬ onDisposeۄח Ѫਸ ਊ೧ࢲ Observer োѾҗ ೧ઁ दਸ ೧ݶ غחؘਃ
Compositionਸ ઙܐೠ റ, ܻ೧ঠ ೡ ٸ ࢎਊೠ.Source: https://medium.com/mobile-app-development-publication/a4867f876928DisposableEffect
ഐ೧ঠ ೞח Lifecycle ೣࣻо ই թই.LifecycleLink: https://navermaps.github.io/android-map-sdk/guide-ko/2-1.html
SavedInstanceStateclass MapViewActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {...mapView?.onCreate(savedInstanceState)}override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)mapView?.onSaveInstanceState(outState)}}View Systemীࢲח onCreate৬ onSaveInstanceStateীࢲ ೣࣻܳ ഐ೧ݶ غҳਃ
SavedInstanceStateval savedInstanceState = rememberSavedInstanceState()val mapView = rememberMapViewWithLifecycle(savedInstanceState)AndroidView(factory = { mapView })@Composablefun rememberSavedInstanceState(): Bundle {return rememberSaveable { Bundle() } // Survives config changes}Composeীࢲח Bundleਸ ҙܻೞח ߑߨਸ ࢎਊೡ ࣻ যਃ ٸ, Bundleਸ rememberSaveable۽ פ
Recomposition, Configuration Changesীࢲ ࢚కܳ ਬೠ.Link: https://developer.android.com/jetpack/compose/state#restore-ui-staterememberSaveableConfiguration Changes
SavedInstanceState@Composablefun rememberMapViewWithLifecycle(savedInstanceState: Bundle): MapView {...DisposableEffect(lifecycle, mapView, savedInstanceState) {val lifecycleObserver = getMapLifecycleObserver(mapView, savedInstanceState.takeUnless { it.isEmpty })onDispose {...mapView.onSaveInstanceState(savedInstanceState)}}return mapView}Ӓېࢲ ҙܻೞח Bundle ёܳ ׳೧ݶ ৮ܐؾפ
SavedInstanceStateprivate fun getMapLifecycleObserver(mapView: MapView,savedInstanceState: Bundle?) = LifecycleEventObserver { _, event ->when (event) {Lifecycle.Event.ON_CREATE -> mapView.onCreate(savedInstanceState)...}}LifecycleEventObserverীࢲب োѾ೧ݶ غѷભ
onLowMemoryoverride fun onLowMemory() {super.onLowMemory()mapView?.onLowMemory()}݄݃ਵ۽ onLowMemory ੑפ Viewীࢲח ೣࣻо ઁҕغҊ যࢲ рױೠؘਃ
onLowMemoryfun MapView.componentCallbacks(): ComponentCallbacks {return object : ComponentCallbacks {override fun onConfigurationChanged(config: Configuration) {}override fun onLowMemory() {[email protected]()}}}Composeীࢲח LifecycleObserverۢ ComponentCallbacksܳ ٜ݅যࢲ ഐೞݶ ؾפ
onLowMemoryfun rememberMapViewWithLifecycle(): MapView {...val lifecycle = LocalLifecycleOwner.current.lifecycleDisposableEffect(lifecycle, mapView) {val callbacks = mapView.componentCallbacks()context.registerComponentCallbacks(callbacks)onDispose {context.unregisterComponentCallbacks(callbacks)}}return mapView}١۾/೧ઁೞח ࠗ࠙ খࢶ җҗ زੌפ
MapViewח View ೡ݅ਸ ೠ.APIܳ ഐೞ۰ݶ ੋఠಕझ ೡਸ ೞח NaverMap ёо ਃೞ.OnMapReadyCallbackLink: https://navermaps.github.io/android-map-sdk/guide-ko/2-1.htmlMapView۽ ب ࠁৈח স լ݅খਵ۽ بܳ ஶ܀ೞח সٜ ਃפ
OnMapReadyCallbackprivate val callback = object : OnMapReadyCallback {override fun onMapReady(naverMap: NaverMap) { ... }}mapView?.getMapAsync(callback)NaverMap ёܳ ਵ۰ݶ,OnMapReadyCallbackਸ ࢎਊೞৈ Callback ഋక۽ оઉয়ݶ ؾפ
OnMapReadyCallbacksuspend fun MapView.awaitMap(): NaverMap {return suspendCoroutine { continuation ->getMapAsync { continuation.resume(it) }}}val coroutineScope = rememberCoroutineScope()val mapView = rememberMapViewWithLifecycle(...)coroutineScope.launch {val naverMap: NaverMap = mapView.awaitMap()}Link: https://developer.android.com/jetpack/compose/side-effects#remembercoroutinescopeComposeীࢲח ܖ౯ਵ۽ оઉয়ח ೣࣻܳ ٜ݅ҳਃਃೡ ٸ CoroutineScopeܳ ࢤࢿ೧ NaverMap ёܳ оઉৢ ࣻ णפ
Compositionਸ ઙܐೠ റ زਵ۽ ஂࣗغب۾ ߧਤо ػ ܖ౯ਸ प೯ೠ.Source: https://medium.com/mobile-app-development-publication/a4867f876928rememberCoroutineScope
OnMapReadyCallbacksuspend fun MapView.awaitMap(): NaverMap {return suspendCoroutine { continuation ->getMapAsync { continuation.resume(it) }}}val coroutineScope = rememberCoroutineScope()val mapView = rememberMapViewWithLifecycle(...)coroutineScope.launch {val naverMap: NaverMap = mapView.awaitMap()}✅ۧѱ NaverMap ёܳ оઉয়ח ؘө ޖࢎ ݃ଢ଼णפ
Use Case 2:ب ২࣌
ب ২࣌mapView.getMapAsync { naverMap ->if (isDarkTheme) {naverMap.mapType = NaverMap.MapType.NavinaverMap.isNightModeEnabled = true} else {naverMap.mapType = NaverMap.MapType.BasicnaverMap.isNightModeEnabled = false}}ঠрݽ٘ա ب ఋੑ ١ਸ ߸҃ೞҊ रݶ,NaverMap ёীо mapType ١ਸ ࢸ೧ݶ ؾפ
ب ২࣌AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()if (isDarkTheme) {naverMap.mapType = NaverMap.MapType.NavinaverMap.isNightModeEnabled = true} else {naverMap.mapType = NaverMap.MapType.BasicnaverMap.isNightModeEnabled = false}}}})Composeীࢲب Ѣ زੌೠ ٘ܳ Ӓ۽ ࢎਊೞݶ غѷणפ
ب ࣘࢿandroid:id="@+id/map_view"android:layout_width="match_parent"android:layout_height="match_parent"app:navermap_extent="31.43,122.37,44.35,132"app:navermap_locationButtonEnabled="true"app:navermap_scaleBarEnabled="false"app:navermap_minZoom="6"app:navermap_zoom="12" />ਵ۽ Layout XMLীࢲ ݻ о ӝࠄੋ ࣘࢿਸ ೡ ࣻ חؘਃ
ب ࣘࢿval context = LocalContext.currentval mapView = remember { MapView(context, naverMapOptions()) }AndroidView(factory = { mapView })private fun naverMapOptions() = NaverMapOptions().extent(LatLngBounds.from(LatLng(31.43, 122.37),LatLng(44.35, 132.0))).locationButtonEnabled(true).scaleBarEnabled(false).minZoom(6.0).camera(CameraPosition(LatLng.INVALID, 12.0))۠ ٜࠗ࠙ب ٘۽ ೞҊ, MapView ࢤࢿী ׳ೞݶ ؾפ ل ࠺तೠ ٘о ٜ݅যભ?
Use Case 3:ب ߮
ب ߮mapView.getMapAsync { naverMap ->naverMap.setOnMapClickListener { _, _ -> /* Do domething*/ }}NaverMapী Click Listenerܳ ١۾ೞח ٘ੑפ
ب ߮AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.setOnMapClickListener { _, _ -> /* Do domething*/ }}}})Composeীࢲب زੌೠ ٘ܳ ࢿೞݶ ؾפ
Use Case 4:݃ழ ࠁৈӝ
݃ழval marker = Marker().apply {captionText = "caption"position = LatLng(..., ...)icon = OverlayImage.fromResource(R.drawable.marker_icon)setOnClickListener {...true}}Viewٚ Composeٚ ࢚ҙহ ݃ழܳ ۧѱ ࢤࢿפ
Example: ӓ ݃ழ ҙܻೞӝvar markers = listOf()fun render(theaters: List) {markers.forEach { it.map = null }markers = theaters.map { theater ->Marker().apply {...map = naverMap}}} ӓ ݾ۾ਸ ݃ழ۽ दೞח ઁ ٘ੑפMarkerী NaverMapਸ ࢸೞҊ, null۽ ೧ઁ೧ঠ פ
Example: ӓ ݃ழ ҙܻೞӝvar markers by remember { mutableStateOf>(emptyList()) }LaunchedEffect(theaters) {val naverMap = mapView.awaitMap()markers.forEach { it.map = null }markers = theaters.map { theater ->Marker().apply {...map = naverMap}}}Link: https://developer.android.com/jetpack/compose/side-effects#launchedeffectComposeীࢲب زੌೠ ٘ܳ ࢿ೮ҳਃ LaunchedEffectܳ ࢎਊ೧ࢲ theaters ߄Չ ٸ݅ ݃ழܳ द Ӓܻب۾ ೮णפ
Composable ߧਤীࢲ suspend funܳ ഐೡ ٸ ࢎਊೠ.Composition द ژח keyо ߸҃ؼ ٸ݃ द ഐػ.LaunchedEffectSource: https://medium.com/mobile-app-development-publication/a4867f876928
Use Case 5:ݫۄ ز
ݫۄ ز// movenaverMap.moveCamera(CameraUpdate.zoomTo(12.0))// animationnaverMap.moveCamera(CameraUpdate.scrollAndZoomTo(LatLng(..., ...), 16.0).animate(CameraAnimation.Fly))Link: https://navermaps.github.io/android-map-sdk/guide-ko/3-2.htmlݫۄܳ زೞח ٘۽, গפݫ࣌ ৈࠗী ٮۄ ࢎਊߑߨ ઑӘ ܵפনೠ APIо ਵפ, ֎ߡب SDK ޙࢲܳ ଵҊೞݶ بؼ Ѫ эणפ
Example: ݃ழ ࢶఖೞӝMarker().apply {setOnClickListener {/* Marker Selected */naverMap.moveCamera(...)}}mapView.getMapAsync { naverMap ->naverMap.setOnMapClickListener { _, _ ->/* Marker Unselected */naverMap.moveCamera(...)}}݃ழ৬ بܳ ܼೞݶ ݫۄܳ زೞח ઁ ٘ੑפ
Example: ݃ழ ࢶఖೞӝvar selectedTheater by remember { mutableStateOf)(null) }Marker().apply {setOnClickListener {/* Marker Selected */selectedTheater = theater}}mapView.getMapAsync { naverMap ->naverMap.setOnMapClickListener { _, _ ->/* Marker Unselected */selectedTheater = null}}Composeীࢲب زੌೞѱ ೡ ࣻ ݅,ࢶఖػ ӓਸ ࢚క۽ ೧فҊ ࢚కо ߸҃ؼ ٸ ݫۄܳ زೞח ߑߨਸ ࢎਊ೮णפ
Composeীࢲ ҙೡ ࣻ ח MutableStateܳ ࢤࢿೞח ೣࣻ.valueо ߸҃غݶ, valueܳ ੍ח Composable ೣࣻ Recomposition ডػ.mutableStateOfLink: https://developer.android.com/jetpack/compose/state#state-in-composablesinterface MutableState : State {override var value: T}var value by remember { mutableStateOf(default) }Recompositionীࢲ valueܳ ਬೞ۰ݶ rememberܳ ࢎਊ೧ঠ ೠ.
Example: ݃ழ ࢶఖೞӝvar selectedTheater by remember { mutableStateOf)(null) }LaunchedEffect(selectedTheater) {val naverMap = mapView.awaitMap()if (selectedTheater != null) {naverMap.moveCamera(...)} else {naverMap.moveCamera(...)}}LaunchedEffectܳ ਊೞৈ, ࢶఖػ ӓ ч ߸҃ؼ ٸ݃ ೠ ਤ۽ ݫۄܳ زೡ ࣻ ѷणפ
ৈӝࢲ ਫ਼Ӭ!Architecture: Unidirectional Data Flowvar name by remember { mutableStateOf("") }OutlinedTextField(value = name,onValueChange = { name = it },label = { Text("Name") })Link: https://developer.android.com/jetpack/compose/architecture ࢚కח ইې۽ زೞҊ ߮ח ਤ۽ زೞח ٣ੋ ಁఢ.
val savedInstanceState = rememberSavedInstanceState()val mapView = rememberMapViewWithLifecycle(savedInstanceState, naverMapOptions())AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.mapType = NaverMap.MapType.NavinaverMap.isNightModeEnabled = truenaverMap.setOnMapClickListener { _, _ -> /* Do domething*/ }}}})খীࢲ ࢿ೮؍ ٘о ࢶഋ UIী ೞѱ ҳഅغח ࢤп೧ࠇद
@Composablefun NaverMap(modifier: Modifier = Modifier,mapOptions: NaverMapOptions = ...,mapProperties: MapProperties = ...,onMapClick: (PointF, LatLng) -> Unit = { _, _ -> },) {val savedInstanceState = rememberSavedInstanceState()val mapView = rememberMapViewWithLifecycle(savedInstanceState, mapOptions)AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.mapType = mapProperties.mapTypenaverMap.isNightModeEnabled = mapProperties.isNightModeEnablednaverMap.setOnMapClickListener(onMapClick)}}})} ٘ܳ хऱࢲ NaverMapۄח Composableਸ ٜ݅ ࣻ णפ
@Composablefun NaverMap(modifier: Modifier = Modifier,mapOptions: NaverMapOptions = ...,mapProperties: MapProperties = ...,onMapClick: (PointF, LatLng) -> Unit = { _, _ -> },) {val savedInstanceState = rememberSavedInstanceState()val mapView = rememberMapViewWithLifecycle(savedInstanceState, mapOptions)AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.mapType = mapProperties.mapTypenaverMap.isNightModeEnabled = mapProperties.isNightModeEnablednaverMap.setOnMapClickListener(onMapClick)}}})}NaverMap(mapOptions = naverMapOptions(),mapProperties = MapProperties(mapType = NaverMap.MapType.Navi,isNightModeEnabled = true,),onMapClick = { _, _ -> /* Do domething*/ })ࢎਊೞח ٘ दੑפӒۢ ࢶഋ UIܳ ҳഅ೮Ҋ ೡ ࣻ ਸөਃ?
@Composablefun NaverMap(modifier: Modifier = Modifier,mapOptions: NaverMapOptions = ...,mapProperties: MapProperties = ...,onMapClick: (PointF, LatLng) -> Unit = { _, _ -> },) {val savedInstanceState = rememberSavedInstanceState()val mapView = rememberMapViewWithLifecycle(savedInstanceState, mapOptions)AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.mapType = mapProperties.mapTypenaverMap.isNightModeEnabled = mapProperties.isNightModeEnablednaverMap.setOnMapClickListener(onMapClick)}}})}🤔࢚కо ߸҃ػݶ?🫠Recompositionীࢲ factoryח द ഐغ ঋח.updateীࢲ јनೡ ࣻח ݅, ݒߣ side-effectܳ ݅٘ח ޙઁ.
Library۽ҕਊചೞӝLink: https://github.com/fornewid/naver-map-composeӘө ࢿೠ ٘ח ೞա জীࢲ ࢎਊೞӝীח ࠙೮݅,ۄ࠳۞ܻ۽ ٜ݅ٸח ҕਊചػ ਬૉாझী ݏࢲ ࢿ೧ঠѷભ?Ӓېࢲ ߣীח খীࢲ ҳഅ೮؍ 5о ਬૉாझܳ ѐࢶ೧ࠁ۰Ҋ פ
ب ࠁৈӝ ✅NaverMap(...) ࠗ࠙ ѱ Ҋச ࠗ࠙ হইө ࢿ೮؍ ٜ٘ਸ Ӓ۽ ࢎਊೞݶ ؾפ
ب ২࣌@Composablefun NaverMap(properties: MapProperties = ...) {...AndroidView(factory = {mapView.apply {coroutineScope.launch {val naverMap = awaitMap()naverMap.mapType = properties.mapTypenaverMap.isNightModeEnabled = properties.isNightModeEnabled}}})ইө ࢿ೮؍ NaverMap ёী mapType ١ਸ ࢸೞח ٘ੑפ
ب ২࣌@Composablefun NaverMap(properties: MapProperties = ...) {...AndroidView(factory = { mapView })LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()naverMap.mapType = properties.mapTypenaverMap.isNightModeEnabled = properties.isNightModeEnabled}}Side-effect APIੋ LaunchedEffectܳ ਊ೧ࢲ Compositionীࢲ݅ ഐغب۾ ߸҃ೡ ࣻ णפ ۞ݶ Recompositionীࢲח द ࢸغ ঋѷભ
ب ২࣌@Composablefun NaverMap(properties: MapProperties = ...) {...AndroidView(factory = { mapView })LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()naverMap.mapType = properties.mapTypenaverMap.isNightModeEnabled = properties.isNightModeEnabled}} propertiesо ߸҃غযب ߈غ ঋח ޙઁ.ೞ݅ propertiesо ߸҃غযب ߈غ ঋח ޙઁо ࢤӤפ
ب ২࣌@Composablefun NaverMap(properties: MapProperties = ...) {...AndroidView(factory = { mapView })val currentProperties by rememberUpdatedState(properties)LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()naverMap.mapType = currentProperties.mapTypenaverMap.isNightModeEnabled = currentProperties.isNightModeEnabled}}Link: https://developer.android.com/jetpack/compose/side-effects#rememberupdatedstaterememberUpdatedState۽ propertiesܳ хऱࢲ ׳ೞݶ ݄݃ী ׳߉ propertiesী Ӕೡ ࣻ णפ
rememberUpdatedStateч ߸҃غযب दೞ ঋইঠ ೞח ബҗ чਸ ଵઑೡ ٸ ࢎਊೠ.Source: https://medium.com/mobile-app-development-publication/a4867f876928
ب ২࣌val currentProperties by rememberUpdatedState(properties)LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()naverMap.mapType = currentProperties.mapTypenaverMap.isNightModeEnabled = currentProperties.isNightModeEnabled}٘۽ द جইоࠇद ٘ীࢲ side-effectо ߊࢤೞח द হભ?
ب ২࣌val currentProperties by rememberUpdatedState(properties)val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap), parentComposition).apply {setContent { MapUpdater(mapProperties = currentProperties) }}try {awaitCancellation()} finally {composition.dispose()}}ࣁࠗੋ ࠗ࠙ ࡅחؘ, पઁ۽ח ۧѱ ࢿ೮णפ
ب ২࣌val currentProperties by rememberUpdatedState(properties)val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap), parentComposition).apply {setContent { MapUpdater(mapProperties = currentProperties) }}try {awaitCancellation()} finally {composition.dispose()}}Link: https://developer.android.com/reference/kotlin/androidx/compose/runtime/#Composition࢜۽ Compositionਸ दೞח ߑߨ.rememberCompositionContext۽ parentCompositionਸ ٜ݅Ҋ Compositionਵ۽ child compositionਸ ٜ݅णפsetContent উীࢲח Composableਸ ࢶೡ ࣻ Ҋ, ݽف աݶ disposeܳ ഐפ
ب ২࣌val currentProperties by rememberUpdatedState(properties)val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap), parentComposition).apply {setContent { MapUpdater(mapProperties = currentProperties) }}try {awaitCancellation()} finally {composition.dispose()}}Link: https://developer.android.com/reference/kotlin/androidx/compose/runtime/Applierب ࣘࢿਸ Composable উীࢲ ҙܻೠ.ب ࣘࢿ MapUpdaterۄח Composable উীࢲ ҙܻೞҳਃ Child Compositionਸ ࢤࢿೡ ٸ, Composable Treeܳ ҙܻೞח Applierܳ ֍णפ
MapApplierח Composition ী MapNode ܻܳ ҙܻೞח ೡ.MapUpdaterח ب ࣘࢿਸ ҙܻೞח ComposeNodeܳ ࢤࢿೠ.MapUpdaterNaverMapMapApplier, MapUpdaterMapApplierMapPropertiesNodeListenersgetMap()MapNodeMapNodeMapNode= ComposeNodeNaverMap@Composable@Composable
MapApplierclass MapApplier(val map: NaverMap,) : AbstractApplier(MapNodeRoot) {private val decorations = mutableListOf()...}interface MapNodeobject MapRootNode : MapNodeclass MapPropertiesNode(val map: NaverMap) : MapNodeMapApplierীࢲ MapNode Listܳ ҙܻೞҊ,MapNode ҳഅ ী MapPropertiesNodeо णפ
MapUpdater@Composablefun MapUpdater(mapProperties: MapProperties) {val map = (currentComposer.applier as MapApplier).mapComposeNode(factory = { MapPropertiesNode(map = map) },update = {set(mapProperties.mapType) { map.mapType = it.value }set(mapProperties.isNightModeEnabled) {map.isNightModeEnabled = it}})Link: https://developer.android.com/reference/kotlin/androidx/compose/runtime/#ComposeNodeMapPropertiesNodeח MapUpdater Composable ղࠗীࢲComposeNode APIܳ ਊ೧ࢲ ࢤࢿೞҳਃ AndroidView APIۢ factoryীࢲ ࢤࢿ, updateীࢲ јन೧ח ഋక۽ غয णפ
MapPropertiesdata class MapProperties(public val mapType: MapType = MapType.Basic,public val isNightModeEnabled: Boolean = false,)@Immutableenum class MapType(public val value: com.naver.maps.map.NaverMap.MapType) {Basic(com.naver.maps.map.NaverMap.MapType.Basic),...}Link: https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8MapPropertiesח mapType э ب ࣘࢿҗ ݒடغח data classੑפ ৈӝࢲ MapType ֎ߡب SDK MapTypeਸ хऱח ഋక۽ غয חؘਃ
MapPropertiesdata class MapProperties(public val mapType: MapType = MapType.Basic,public val isNightModeEnabled: Boolean = false,)@Immutableenum class MapType(public val value: com.naver.maps.map.NaverMap.MapType) {Basic(com.naver.maps.map.NaverMap.MapType.Basic),...}Link: https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8Compose ஹੌ۞о प೯غ ঋח ݽٕ ېझח ࠛউೠ Ѫਵ۽ ୶ۿػ.wrapper classী ࠛ߸ਸ ೞৈ Recompositionਸ ୭ࣗചೡ ࣻ .Ӓ ਬח, ֎ߡب SDKח Compose ஹੌ۞о प೯غ ঋই Composeীࢲח ۠ ېझܳ unstable۽ ୶ۿೞӝ ٸޙੑפ@immutableਸ ୶оೞৈ ࠛਃೠ recompositionਸ ੌ ࣻ णפ
ب ২࣌ ✅NaverMap(...mapProperties = MapProperties(mapType = NaverMap.MapType.Navi,isNightModeEnabled = true,),...)ب ২࣌ਸ ࢚క۽ ׳ೞח ࠗ࠙ਸ ৮ࢿ೮णפ
Use Case 3:ب ߮ઁ ب ߮۽ ֈযоࠁѷणפ
ب ২࣌@Composablefun NaverMap(properties: MapProperties = ...) {...val currentProperties by rememberUpdatedState(properties)val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap),parentComposition).apply {setContent { MapUpdater(mapProperties = currentProperties) }}ߑӘө ࠄ ب ২࣌ ٘۽, propertiesܳ ׳೧ח ࠗ࠙ਸ ࢿ೮חؘਃ
ب ߮@Composablefun NaverMap(onMapClick: (PointF, LatLng) -> Unit = { _, _ -> }) {...val mapClickListeners = remember { MapClickListeners() }.also { it.onMapClick = onMapClick }val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap),parentComposition).apply {setContent { MapUpdater(clickListeners = mapClickListeners) }}ب ߮ীࢲח onMapClickۄח ۈܳ ׳פ
ب ߮@Composablefun NaverMap(onMapClick: (PointF, LatLng) -> Unit = { _, _ -> }) {...val mapClickListeners = remember { MapClickListeners() }.also { it.onMapClick = onMapClick }val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap),parentComposition).apply {setContent { MapUpdater(clickListeners = mapClickListeners) }}class MapClickListeners {var onMapClick: (PointF, LatLng) -> Unit by mutableStateOf({ _, _ -> })}MapClickListenersח onMapClick ۈܳ MutableState۽ ҙܻೞҊ,recompositionীࢲ MapClickListenersо ߄Շ ঋب۾ remember۽ ӝর೧સפ
ب ߮@Composablefun NaverMap(onMapClick: (PointF, LatLng) -> Unit = { _, _ -> }) {...val mapClickListeners = remember { MapClickListeners() }.also { it.onMapClick = onMapClick }val parentComposition = rememberCompositionContext()LaunchedEffect(Unit) {val naverMap = mapView.awaitMap()val composition = Composition(MapApplier(naverMap),parentComposition).apply {setContent { MapUpdater(clickListeners = mapClickListeners) }}Ӓېࢲ MapUpdater ղࠗীࢲ ߮о ߊࢤೞݶ ۠ ࣽࢲ۽ ߮о ࢚ਤ۽ ؾפ
// Compositionب ߮@Composablefun MapUpdater(clickListeners: MapClickListeners) {ComposeNode(factory = { MapPropertiesNode(clickListeners = clickListeners) },update = { update(clickListeners) { this.clickListeners = it } },)}class MapPropertiesNode(var map: NaverMap,var clickListeners: MapClickListeners,) : MapNode {override fun onAttached() {map.setOnMapClickListener { point, coord -> clickListeners.onMapClick(point, coord) }}MapUpdaterীࢲח Composition दী clickListenersܳ ׳೧Ҋ
// Recompositionب ߮@Composablefun MapUpdater(clickListeners: MapClickListeners) {ComposeNode(factory = { MapPropertiesNode(clickListeners = clickListeners) },update = { update(clickListeners) { this.clickListeners = it } },)}class MapPropertiesNode(var map: NaverMap,var clickListeners: MapClickListeners,) : MapNode {override fun onAttached() {map.setOnMapClickListener { point, coord -> clickListeners.onMapClick(point, coord) }}Recomposition दীח ۈо ߄Շਸ ࣻ ਵפ MapClickListenersܳ द Ү೧સפ
ب ߮@Composablefun MapUpdater(clickListeners: MapClickListeners) {ComposeNode(factory = { MapPropertiesNode(clickListeners = clickListeners) },update = { update(clickListeners) { this.clickListeners = it } },)}class MapPropertiesNode(var map: NaverMap,var clickListeners: MapClickListeners,) : MapNode {override fun onAttached() {map.setOnMapClickListener { point, coord -> clickListeners.onMapClick(point, coord) }}MapPropertiesNodeо attachغݶ NaverMapী Listenerܳ ١۾פ
ب ߮@Composablefun MapUpdater(clickListeners: MapClickListeners) {ComposeNode(factory = { MapPropertiesNode(clickListeners = clickListeners) },update = { update(clickListeners) { this.clickListeners = it } },)}class MapPropertiesNode(var map: NaverMap,var clickListeners: MapClickListeners,) : MapNode {override fun onAttached() {map.setOnMapClickListener { point, coord -> clickListeners.onMapClick(point, coord) }}֎ߡبীࢲ ߮о ߊࢤೞݶ ۠ ࣽਵ۽ ࢚ਤ۽ ؾפ
ب ߮ ✅NaverMap(...onMapClick = { _, _ -> /* Do domething*/ }...)ۧѱ ֎ߡبীࢲ ߮ܳ ܖח ࠗ࠙ب ҳഅ೧ࠌणפ
Use Case 4:݃ழ ࠁৈӝ݃ழܳ ࠁৈח ࠗ࠙ਸ द ಝࠁѷणפ
// State݃ழval marker = Marker().apply {captionText = "caption"position = LatLng(..., ...)icon = OverlayImage.fromResource(R.drawable.marker_icon)setOnClickListener {...true}map = naverMap}ۧѱ ࢚కܳ ೞח ࠗ࠙ Ҋ
// Event݃ழval marker = Marker().apply {captionText = "caption"position = LatLng(..., ...)icon = OverlayImage.fromResource(R.drawable.marker_icon)setOnClickListener {...true}map = naverMap}߮ী ೠ ࠗ࠙ Ҋ
// Display݃ழval marker = Marker().apply {captionText = "caption"position = LatLng(..., ...)icon = OverlayImage.fromResource(R.drawable.marker_icon)setOnClickListener {...true}map = naverMap}֎ߡبী ࠁৈח ࠗ࠙ णפ
݃ழMarker(captionText = "caption",position = LatLng(..., ...),icon = OverlayImage.fromResource(R.drawable.marker_icon),onClick = {...true},)Composeীࢲח ۠ ഋక۽ ࢎਊೡ ࣻ ѱ ٜ݅णפ
݃ழMarker(captionText = "caption",position = LatLng(..., ...),icon = OverlayImage.fromResource(R.drawable.marker_icon),onClick = {...true},)@Composablefun Marker(captionText: String? = ...,position: LatLng = ...,icon: OverlayImage = ...,onClick: (Marker) -> Boolean = { false },) { ... }ۢ ComposeNodeܳ ഝਊ೧ࢲMarker Composableਸ ٜ݅ݶ ؾפ
ComposeNodeܳ ࢤࢿೞৈ ࢚కܳ সؘ. MapApplierܳ ా೧ بী दೠ.MarkerNaverMapMarker ҳઑMapApplierMarkerNodeMarkeronClickgetMap()MapNodeMapNodeMapNode= ComposeNodeNaverMap@Composable@ComposableLink: https://github.com/fornewid/naver-map-compose/.../com/naver/maps/map/compose/Marker.kt
݃ழMarker(captionText = "caption",position = LatLng(..., ...),icon = OverlayImage.fromResource(R.drawable.marker_icon),onClick = {...true},)// map = naverMap ?NaverMapী द೧ח ࠗ࠙ ٘۽ যڌѱ അೞݶ જਸөਃ?
݃ழNaverMap(...) {Marker(captionText = "caption",position = LatLng(..., ...),icon = OverlayImage.fromResource(R.drawable.marker_icon),onClick = {...true},)}ۧѱ NaverMap Composable ղࠗীChild Composable۽ ࢶ೧ݶ ؾפ
Example: ӓ ݃ழ ҙܻೞӝvar markers by remember { mutableStateOf>(emptyList()) }LaunchedEffect(theaters) {val naverMap = mapView.awaitMap()markers.forEach { it.map = null }markers = theaters.map { theater ->Marker().apply {...map = naverMap}}} ష ۽ંীࢲ ࢿ೮؍ ٘חUIܳ Side-effect۽ ҙܻೞח ו՝חؘਃ
Example: ӓ ݃ழ ҙܻೞӝNaverMap(...) {theaters.forEach { theater ->Marker(...)}}ઁ ખ ؊ ࢶഋ UIী оӰѱ ݃ழܳ दೡ ࣻ णפ
݃ழ ࠁৈӝ ✅NaverMap {Marker(position = LatLng(..., ...),...onClick = {/* Do domething*/true},)}
Use Case 5:ݫۄ زࢎਊо بܳ झ܀೧ࢲ ݫۄ ਤܳ زೠ
Use Case 5:ݫۄ زചݶਸ ഥೞݶ
Use Case 5:ݫۄ زഥ द, ݫۄ ਤо ୡӝചغח ޙઁ!
Use Case 5:ݫۄ زConfig Changes दী ࢚కܳ ਬೡ ࣻ হਸө?rememberSaveable
ݫۄী ೠ ࢚క, ߮ܳ ҙܻೞח State Holder.MapUpdaterCameraPositionStateNaverMap MapPropertiesNodeCameraPositionStateCameraPosition NaverMap ListenersCompositiononlyState HolderEventNew CameraPosition@ComposableCompositionীࢲ݅ ݄݃ী ೠ ਤ۽ ݫۄܳ زפ
࢚క ҙܻ: State HolderLink: https://developer.android.com/jetpack/compose/state#state-holder-source-of-truthࠂೠ UI ࢚క৬ UI ۽ਸ ನೣೞח ېझ. Composable ࠂࢿਸ ৈળ.
internal class MapPropertiesNode(val map: NaverMap,cameraPositionState: CameraPositionState,) : MapNode {init {cameraPositionState.setMap(map)}private val cameraIdleListener = NaverMap.OnCameraIdleListener {cameraPositionState.rawPosition = map.cameraPosition}private val cameraChangeListener = NaverMap.OnCameraChangeListener { _, _ ->cameraPositionState.rawPosition = map.cameraPosition}override fun onAttached() {map.addOnCameraIdleListener(cameraIdleListener)map.addOnCameraChangeListener(cameraChangeListener)}}MapPropertiesNode ٘ܳ ಝࠇद
internal class MapPropertiesNode(val map: NaverMap,cameraPositionState: CameraPositionState,) : MapNode {init {cameraPositionState.setMap(map)}private val cameraIdleListener = NaverMap.OnCameraIdleListener {cameraPositionState.rawPosition = map.cameraPosition}private val cameraChangeListener = NaverMap.OnCameraChangeListener { _, _ ->cameraPositionState.rawPosition = map.cameraPosition}override fun onAttached() {map.addOnCameraIdleListener(cameraIdleListener)map.addOnCameraChangeListener(cameraChangeListener)}} cameraPositionStateী NaverMap ёܳ ࢸ೧Ҋ Listenerܳ ా೧, ߄Ո ݫۄ ਤо ٜযয়ݶ rawPositionਸ সؘ ೧સפ
public class CameraPositionState(position: CameraPosition = ...) {internal var rawPosition by mutableStateOf(position)public var position: CameraPositionget() = rawPositionset(value) { ... }internal fun setMap(map: NaverMap?) {this.map = mapif (map != null) {map.moveCamera(CameraUpdate.toCameraPosition(position))}}}NaverMap द ࢸغݶ(=Composition), ݄݃ ਤ۽ زೠ.NaverMap द ࢸغݶ Compositionਵ۽ ౸ױೞҊ, ݄݃ ਤ۽ زदெח ܻо ٜযо णפ
@Composablepublic inline fun rememberCameraPositionState(key: String? = null,crossinline init: CameraPositionState.() -> Unit = {},): CameraPositionState = rememberSaveable(key = key,saver = CameraPositionState.Saver) {CameraPositionState().apply(init)}public val Saver: Saver = Saver(save = { it.position },restore = { CameraPositionState(it) })Config Changes दী݄݃ ࢚కܳ ೧ળ.Link: https://developer.android.com/jetpack/compose/state#restore-ui-staterememberSaveableܳ ਊ೧ࢲ Config Changes दী ݄݃ ࢚కܳ /ࠂҳ ೧સפ
val cameraPositionState = rememberCameraPositionState()NaverMap(cameraPositionState = cameraPositionState)val coroutineScope = rememberCoroutineScope()coroutineScope.launch {cameraPositionState.animate(CameraUpdate.scrollTo(position),animation = CameraAnimation.Fly,durationMs = 1_000)}ݫۄ ز ✅ࢎਊೡ ٸח NaverMap Composableী State Holderܳ ׳೧Ҋزೡ ٸח ܖ౯ਸ ਊೞৈ গפݫ࣌ ܻܳ ೡ ࣻ णפ
● Composable Lifecycle- Composition- Recomposition● Architecture- Unidirectional Data Flow- State Holder● State- remember- rememberSaveable- mutableStateOf- @ImmutableKeywords● Side-effects- LaunchedEffect- DisposableEffect- rememberCoroutineScope- rememberUpdatedState● Interoperability APIs- AndroidView- setContent● Custom Composition- rememberCompositionContext- Composition- Applier, ComposeNode
ਃೠ ӝמ ਵݶ, गܳ ١۾೧ࣁਃ!👆
Q & Aхࢎפ!