note11g / flutter_naver_map

Naver Mobile Dynamic Map SDK for Flutter (unofficial)
BSD 3-Clause "New" or "Revised" License
143 stars 69 forks source link

onCameraIdle 또는 onCameraChange 에서 setState 변경하면 지도 아이콘 깜빡임 #163

Closed XOneto9 closed 9 months ago

XOneto9 commented 10 months ago

아이폰에서

    onCameraChange: ((reason, animated) {}),
    onCameraIdle: () {}

두 함수 중에 setState 혹은 상태를 변화시키는 함수가 들어갈 경우 지도에 기본적으로 표기되는 아이콘들이 깜빡입니다. 맵이 re-load 되는 것 같습니다.

https://github.com/note11g/flutter_naver_map/assets/18350072/5e521449-e460-4433-aaf9-bbac86a23519

note11g commented 10 months ago

Android, iOS 모두에서 재현에 실패하였습니다. 재현 가능한 샘플 코드를 자세히 부탁드리겠습니다. 감사합니다.

XOneto9 commented 10 months ago

@note11g 특별히 뭔가 복잡하게 구현한건 아니구요...ㅎㅎ 혹시 모르니 전체 코드 올릴게요!

state 상태는 GetX를 사용하고 있구요

onCameraIdle: () {
                    locationController.updateResearch(true);
                  },

이부분에 locationController.updateResearch(true)는 getx 의 controller파일에서 해당 함수를 불러왔습니다.

locationController

updateResearch(bool val) {
    reSearch = val;
    update();
  }

reSearch는 만약 화면으로 이동을 했다면 reSearch 함수를 true로 변경하고 이를 main page에서 '여기서 다시검색' 버튼을 보여주게 됩니다.

naverMap

import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:flutter_naver_map/flutter_naver_map.dart';
import 'package:get/get.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:store9_app/Controller/location_controller.dart';
import 'package:store9_app/Controller/store_controller.dart';

class NaverMapPage extends StatefulWidget {
  const NaverMapPage({super.key});

  @override
  State<NaverMapPage> createState() => _NaverMapPageState();
}

class _NaverMapPageState extends State<NaverMapPage> {
  final scaffoldKey = GlobalKey<ScaffoldState>();
  late NaverMapController naverController;
  final locationController = Get.find<LocationController>();
  final storeController = Get.find<StoreController>();

  // _onMapTap(LatLng position) {
  //   locationController.updateClear();
  // }

  // void _onCameraChange(
  //     LatLng latLng, CameraChangeReason reason, bool isAnimated) {
  //   locationController.changeScreenCoordinate(
  //     latitude: latLng.latitude,
  //     longitude: latLng.longitude,
  //   );
  // }

  @override
  void dispose() {
    naverController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      body: Stack(
        children: [
          GetBuilder<LocationController>(
            builder: (_) {
              if (_.isLoading) {
                return LoadingAnimationWidget.staggeredDotsWave(
                  color: context.theme.colorScheme.onBackground,
                  size: 50,
                );
              } else {
                return NaverMap(
                  options: NaverMapViewOptions(
                    initialCameraPosition: NCameraPosition(
                      target: NLatLng(
                        _.currentLocation.latitude,
                        _.currentLocation.longitude,
                      ),
                      zoom: 15,
                    ),
                  ),
                  forceGesture: false,
                  onMapReady: (NaverMapController controller) {
                    controller.setLocationTrackingMode(
                        NLocationTrackingMode.noFollow);
                    final position = storeController
                        .myStore!.store_information.location.coordinates;
                    final marker = NMarker(
                        id: "mystore",
                        position: NLatLng(position[1], position[0]));
                    final markerIcon = NOverlayImage.fromWidget(widget: Icon(MaterialCommunityIcons.circle), size: Size(20, 20), context: context);
                    marker.setIcon(markerIcon);
                    marker.setSize(Size(30, 42));
                    marker.setIconTintColor(Colors.black);
                    marker.setOnTapListener((overlay) {
                      final cameraUpdate = NCameraUpdate.scrollAndZoomTo(
                        target: overlay.position,
                        zoom: 15,
                      );
                      naverController.updateCamera(cameraUpdate);
                    });
                    controller.addOverlay(marker);
                    setState(() {
                      naverController = controller;
                    });
                  },
                  onMapTapped: (point, latLng) {
                    final cameraUpdate = NCameraUpdate.scrollAndZoomTo(
                      target: latLng,
                      zoom: 15,
                    );
                    naverController.updateCamera(cameraUpdate);
                  },
                  onSymbolTapped: (symbol) {},
                  onCameraChange: ((reason, animated) {}),
                  onCameraIdle: () {
                    locationController.updateResearch(true);
                  },
                  onSelectedIndoorChanged: (indoor) {},
                );
              }
            },
          ),
          Align(
            alignment: Alignment.bottomRight,
            child: GestureDetector(
              child: Container(
                width: 45,
                height: 45,
                margin: EdgeInsets.only(right: 16, bottom: 48),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(45),
                  color: Colors.white,
                  boxShadow: const [
                    BoxShadow(
                      color: Colors.black26,
                      blurRadius: 2,
                    )
                  ],
                ),
                child: Icon(
                  MaterialIcons.navigation,
                  color: Colors.black,
                ),
              ),
              onTap: () async {
                final mode = await naverController.getLocationTrackingMode();
                if (mode == NLocationTrackingMode.noFollow) {
                  naverController
                      .setLocationTrackingMode(NLocationTrackingMode.follow);
                } else if (mode == NLocationTrackingMode.follow) {
                  naverController
                      .setLocationTrackingMode(NLocationTrackingMode.face);
                } else {
                  naverController
                      .setLocationTrackingMode(NLocationTrackingMode.noFollow);
                }
              },
            ),
          ),
        ],
      ),
    );
  }
}

main page

class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
@override
  Widget build(BuildContext context) {
    final headerHeight = appStatusBarHeight + appBarHeight;
    return Scaffold(
      body: Stack(
        children: [
          NaverMapPage(), // naver map module
          GetBuilder<LocationController>(builder: (_) { //Location controller에서 reSearch 변수를 불러옴
            if (_.reSearch) {
              return _searchHearButtonBuild(headerHeight); // 여기서 검색 textField
            } else {
              return Container();
            }
          }),
          _buildFloatingSearchBar(appBarHeight), //검색 textField component
        ],
      ),
    );
  }

}
note11g commented 10 months ago

iOS에서 재현 성공하였습니다. (Android 해당 사항 없음) 해당 현상은 PlatformView가 다시 렌더링되며 발생하는 현상인 것으로 보입니다. 가장 바람직한 방법은 NaverMap 위젯을 렌더 범위에서 제외시키는 것입니다. Minimal ReBuild 전략을 사용하시는 것을 권장드립니다. 그래도, 해당 사항 최대한 불편함 없도록 수정할 수 있는 방안이 있는지 검토해보도록 하겠습니다. 감사합니다.

note11g commented 9 months ago

@XOneto9 다시 해당 이슈를 확인해보았습니다. 해당 이슈가 발생하기 위해서는 지도가 다시 그려져야 하는데, 이는 options의 값이 바뀌지 않는 이상 다시 그려지지 않습니다. (주소값은 변해도, 값으로 검증하므로 값이 변경되어져야만 다시 그려집니다.)

테스트를 위한 코드와, 해당 테스트 영상을 첨부합니다.

https://github.com/note11g/flutter_naver_map/assets/67783062/c206ca54-a477-4505-b9c5-ac5da0515c88

class MapExamplePage extends StatefulWidget {
  const MapExamplePage({super.key});

  @override
  State<MapExamplePage> createState() => _MapExamplePageState();
}

class _MapExamplePageState extends State<MapExamplePage> {
  final option = const NaverMapViewOptions(
      initialCameraPosition:
          NCameraPosition(target: NLatLng(37.2, 127), zoom: 14));

  bool isCameraIdle = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Stack(children: [
      NaverMap(
          onCameraChange: (_, __) => setState(() => isCameraIdle = false),
          onCameraIdle: () => setState(() => isCameraIdle = true),
          options: option),
      Positioned.fill(
          top: null,
          bottom: MediaQuery.paddingOf(context).bottom,
          child: Center(
              child: Text(DateTime.now().toIso8601String(),
                  style: TextStyle(
                      backgroundColor:
                          isCameraIdle ? Colors.lightGreen : Colors.redAccent,
                      fontWeight: FontWeight.bold)))),
    ]));
  }
}

따라서, 해당 현상은 NaverMapViewOptions의 값을 변경하지 않는다면, setState를 호출해도 발생하지 않습니다. 다시 오류가 발생하시거나 추가 질문이 있으시다면, 이슈를 다시 열고 코멘트 남겨주세요. 감사합니다. 좋은 주말 되세요.