[프로젝트-DediCats] Devlog-4

김대연·2020년 2월 10일
0

Project DediCats

목록 보기
5/16

어제 지도맵을 관련해서 글을 작성했는데, 몇가지 버그가 있었다.

일단 현재 mobX를 이용해 현재 position, region, boundBox 와 이와 관련된 함수들을 저장하고 있는 상태이다.

componentDidMount에서 위치 권한을 허용하면서 navigator.geolocation.watchPosition을 이용해 position을 받아 위의 변수들에 값을 할당하는 과정으로 작성했다. 문제는 정상적으로 렌더 되다가도 새로고침을 하면 가끔 렌더할 때 위경도가 0,0으로 잡히는 문제가 발생했다.

스택오버플로우를 통해 코드를 수정하여 아래가 수정된 버젼이다.

먼저 MapView를 리턴하는 MainMap.

/* eslint-disable react/state-in-constructor */
import React from 'react';
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
import {
  StyleSheet,
  View,
  Dimensions,
  Platform,
  Text,
} from 'react-native';
import Carousel from 'react-native-snap-carousel';
import { inject, observer } from 'mobx-react';
import BriefCatInfo from './BriefCatInfo';
import MainMarker from './MainMarker';
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
  },
  map: {
    width,
    height,
    ...StyleSheet.absoluteFillObject,
  },
  carousel: {
    position: 'absolute',
    bottom: 0,
    marginBottom: 10,
  },
});

class MainMap extends React.Component {
  state = {
    isShowingCarousel: false,
  };

  componentDidMount() {
    console.log('before Did Mount');
    this.props.requestMapPermission().then(() => {
      console.log('After Did Mount');
    });
  }

  componentWillUnmount() {
    navigator.geolocation.clearWatch(this.props.watchId);
  }

  renderCarouselItem = ({ item }) => {
    const { isShowingCarousel } = this.state;

    if (isShowingCarousel) {
      return (
        <BriefCatInfo
          item={item}
          isShowingCarousel={isShowingCarousel}
          hideCarousel={this.hideCarousel}
        />
      );
    }
  };

  hideCarousel = () => {
    this.setState({ isShowingCarousel: false });
  }

  onCarouselItemChange = (index) => {
    const { markers } = this.props;
    const location = markers[index];
    const region = {
      latitude: location.latitude,
      longitude: location.longitude,
      latitudeDelta: 0.005,
      longitudeDelta: 0.005,
    };
    this._map.animateToRegion(region);
  }

  onMarkerPressed = (location, index) => {
    const { isShowingCarousel } = this.state;
    const region = {
      latitude: location.latitude,
      longitude: location.longitude,
      latitudeDelta: 0.005,
      longitudeDelta: 0.005,
    };
    this._map.animateToRegion(region);
    this._carousel.snapToItem(index);
    if (!isShowingCarousel) {
      this.setState({ isShowingCarousel: true });
    }
    console.log(isShowingCarousel);
  }

  render() {
    console.disableYellowBox = 'true';
    const { markers, onRegionChangeComplete, currentRegion, permissionState } = this.props;
    console.log('render latitudeDelta: ', currentRegion.latitudeDelta);
    if (permissionState === true) {
      return (
        <View style={styles.container}>
          <MapView
            provider={PROVIDER_GOOGLE}
            ref={(map) => this._map = map}
            style={styles.map}
            showsUserLocation={true}
            region={{...currentRegion}}
            onRegionChangeComplete={onRegionChangeComplete}
          >
            {
              markers.map((marker, index) => (
                <MainMarker
                  key={marker.name}
                  marker={marker}
                  index={index}
                  onMarkerPressed={this.onMarkerPressed}
                  currentRegion={currentRegion}
                />
              ))
            }
          </MapView>
          <Carousel
            ref={(c) => { this._carousel = c; }}
            data={markers}
            renderItem={this.renderCarouselItem}
            onSnapToItem={(index) => this.onCarouselItemChange(index)}
            removeClippedSubviews={false}
            sliderWidth={width}
            itemWidth={300}
            containerCustomStyle={styles.carousel}
          />
        </View>
      )
    } else {
      return (      
        <View>
          <Text style={{flex: 1}}>No Permission for location</Text>
        </View>
      )
    }
  }
}

export default inject(({ cat, user }) => ({
  markers: cat.markers,
  currentRegion: user.currentRegion,
  currentBoundingBox: user.currentBoundingBox,
  onRegionChangeComplete: user.onRegionChangeComplete,
  permissionState: user.permissionState,
  watchId: user.watchId,
  requestMapPermission: user.requestMapPermission,
}))(
  observer(MainMap),
);

그리고 위의 MainMap에서 props로 받아오는 observableaction을 포함하는 UserStore.

/* eslint-disable arrow-parens */
import { observable, action, computed, decorate, runInAction } from 'mobx';
import * as Location from 'expo-location';
import * as Permissions from 'expo-permissions';

class UserStore {
  constructor(root) {
    this.root = root;
  }
  //observable
  // 현재 나의 위치
  currentPosition = {
    latitude: 0,
    longitude: 0,
  };

  addCatMarker = {
    latitude: 0,
    longitude: 0,
  }

  // 현재 화면의 위치
  currentRegion = {
    latitude: 0,
    latitudeDelta: 0,
    longitude: 0,
    longitudeDelta: 0,
  }

  // 현재 화면의 범위
  currentBoundingBox = {
    NElatitude: 0,
    NElongitude: 0,
    SWlatitude: 0,
    SWlongitude: 0,
  };

  permissionState = false;

  watchId = null;

  // action
  requestMapPermission = async () => {
    try {
      console.log('before askAsync');
      const { status } = await Permissions.askAsync(Permissions.LOCATION);
      if (status === 'granted') {
        console.log('Granted');
        console.log('before watchPosition');
        this.watchId = navigator.geolocation.watchPosition(
          (position) => {
            const { latitude, longitude } = position.coords;
            console.log('before set permisiionState');
            this.permissionState = true;
            console.log('after set permisiionState');
            console.log('before set currentPosition');
            this.currentPosition = {
              latitude,
              longitude,
            };
            console.log('after set currentPosition');
            console.log('before set currentRegion');
            this.currentRegion = {
              latitude,
              latitudeDelta: 0.005,
              longitude,
              longitudeDelta: 0.005,
            };
            console.log('after set currentRegion');
            console.log('before set getBoundingBox');
            this.getBoundingBox(this.currentRegion);
            console.log('after set getBoundingBox');
          },
          (error) => { Alert.alert(error.code, error.message); },
          { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 },
        );
      console.log('after watchPosition');
      } else {
        console.log('not Granted');
        this.permissionState = false;
      }
    } catch (err) {
      console.warn(err)
    }
  }

  getBoundingBox = (region) => {
    this.currentBoundingBox = {
      NElatitude: region.latitude + region.latitudeDelta / 2, // northLat - max lat
      NElongitude: region.longitude + region.longitudeDelta / 2, // eastLng - max lng
      SWlatitude: region.latitude - region.latitudeDelta / 2, // southLat - min lat
      SWlongitude: region.longitude - region.longitudeDelta / 2, // westLng - min lng
    };
  };

  onRegionChangeComplete = (region) => {
    this.currentRegion = region;
    this.getBoundingBox(region);
  }
}

decorate(UserStore, {
  currentPosition: observable,
  currentRegion: observable,
  currentBoundingBox: observable,
  permissionState: observable,
  watchId: observable,
  addCatMarker: observable,
  requestMapPermission: action,
  getLocationPermission: action,
  getWatchPosition: action,
  setUserLocation: action,
  setUserRegion: action,
  getBoundingBox: action,
  onRegionChangeComplete: action,
});

export default UserStore;

위 코드 중 requestMapPermission 에서 비동기적인 처리가 제대로 이루어지지 않는 것 같다. 좀 더

부가적으로 Marker를 리턴하는 MainMarker이다.

import React from 'react';
import { Marker } from 'react-native-maps';
import { View } from 'react-native';

export default class MainMarker extends React.Component {
  state = { tracksViewChanges: true, }

  stopRendering = () => {
    this.setState({ tracksViewChanges: false });
  }

  render() {
    const { marker, index, currentRegion } = this.props;
    // 1. latitudeDelta보다 큰 값에서는 GET 과 Render 둘 다 막는다.
    // 2. latitudeDelta보다 작으면 마커들을 렌더하고, 슬라이드를 해서 범위(Bound)가 변경되었을 시에 GET을 보낸다.
    if (currentRegion.latitudeDelta < 0.007 || currentRegion.longitudeDelta < 0.007) {
      return (
        <Marker
          coordinate={{
            latitude: marker.latitude,
            longitude: marker.longitude,
          }}
          onPress={() => this.props.onMarkerPressed(marker, index)}
          tracksViewChanges={this.state.tracksViewChanges}
          onLoad={this.stopRendering}
        />
      );
    } else {
      return <View></View>;
    }
  }
}

위의 코드로 수정하고 나서 위경도 0,0을 위치하는 부분은 사라졌지만 아래 사진처럼 저희가 설정한 Delta값과는 다른 모습으로 렌더한다.

지정한 Delta 값은 0.005 이기 때문에 아파트 단지 수준으로 확대되어야 하는데 위의 사진은 확실히 렌더가 이상했다.

UserStore에 있는 action 함수과 MainMap render()에게 디버그용 콘솔을 달아 실행시켰더니 터미널에 아래와 같이 찍혔다.

Running application on iPhone 11 Pro Max.
render region:  Object {
  "latitude": 0,
  "latitudeDelta": 0,
  "longitude": 0,
  "longitudeDelta": 0,
}
before Did Mount
before askAsync
Granted
before watchPosition
render region:  Object {
  "latitude": 0,
  "latitudeDelta": 0,
  "longitude": 0,
  "longitudeDelta": 0,
}
after watchPosition
After Did Mount
before set permisiionState
render region:  Object {
  "latitude": 0,
  "latitudeDelta": 0,
  "longitude": 0,
  "longitudeDelta": 0,
}
after set permisiionState
before set currentPosition
after set currentPosition
before set currentRegion
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set currentRegion
before set getBoundingBox
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set getBoundingBox
before set permisiionState
after set permisiionState
before set currentPosition
after set currentPosition
before set currentRegion
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set currentRegion
before set getBoundingBox
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set getBoundingBox
before set permisiionState
after set permisiionState
before set currentPosition
after set currentPosition
before set currentRegion
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set currentRegion
before set getBoundingBox
render region:  Object {
  "latitude": 37.503528,
  "latitudeDelta": 0.005,
  "longitude": 127.049784,
  "longitudeDelta": 0.005,
}
after set getBoundingBox
render region:  Object {
  "latitude": 7.377204933160215,
  "latitudeDelta": 155.64922429392777,
  "longitude": 127.04978093504906,
  "longitudeDelta": 130.54316237568855,
}

componentDidMount안에 콜백의 실행이 비동기적이지 않은 것을 확인할 수 있다. 이 부분을 해결한 후 그 과정을 다음에 포스트해야겠다.

0개의 댓글