Skip to content

[K011] 노균욱

BENDENG1 edited this page Dec 10, 2023 · 10 revisions

개인회고

WEEK 1

💡 프로젝트 개인 목표

많이 부족하지만 끝까지 포기하지 않고 항상 ‘왜?’ 라는 의문을 던지면서 끊임없이 자신에게 던지는 태도와
그 동안 배운 기술들의 적용과 프로젝트를 팀원분들과 같이 잘 마무리 했으면 좋겠습니다!


💡 1주간 느낀 점 (기획단계)

생각보다 기획 단계는 중요하다는 것을 깨달았다.
지난 2번의 간단했던 프로젝트를 했을 때는 간단한 디자인과 명세서 만을 작성해보았습니다.
지난 프로젝트들과는 달리 이번 프로젝트는 기획 단계에 대한 중요성을 배우며 설립하는 한 주간의 시간이 존재했습니다.
그 덕분에 기획 단계에서 많은 것들을 기획에서 잘 잡아야 향후 설계 단계에서 원활하게 진행이 될 것이라고 느꼈습니다.


💡 1주 간 진행 한 부분

  1. 프로젝트 아이디어 빌딩
  2. 기획 상세
  3. 협업 규칙(컨벤션, 규칙)
  4. featureList
  5. 와이어 프레임
  6. BackLog
  7. 디자인 구성

의 순서로 진행 중이며 현재는 분업을 통해 디자인을 구성하고 있습니다.


💡 1주 간 개인적으로 어려웠거나 개선한 부분

  • featureList 와 BackLog에 대해서 작성을 할 때 스토리에 대해서 너무 추상적으로 적어야할지 상세하게 적어야 할지 약간은 Task와 스토리의 애매모호했던 지점이었다고 생각하여 구상을 할 때 조금은 어려웠습니다.
  • MVP 버전 적용 및 기능 삭제
    • 리뷰어님들과 첫 기획 발표에 더불어 팀원 분들과 회의를 통해 기능적으로 욕심이 많다는 것을 판단하여 몇 가지 기능들을 삭제를 고려하였는데 (대표적으로 채팅) 삭제할 때 조금은 아쉬움을 느꼈지만 현실적으로 정해진 기간을 고려하여 수정을 하는데 난항을 겪었습니다.
    • MVP버전 즉 최소 기능을 통해 1.0 버전을 만들어야 하는 상황에서 소셜로그인 3개 맛집 리스트의 분류도 많았던 점을 고려해 이 부분에 대해서 중요도를 판단하여 필수적인 기능만을 위해 버전을 MVP, FINAL로 구분하였습니다.
  • 디자인
    • 안드로이드 3분께서 같이 만든 와이어 프레임을 바탕으로 디자인을 구성을 하는데 미적인 감각은 없을뿐더러 피그마 작업을 처음 진행해서 그런지 나름 노력을 해보았지만 처음 스마트폰이 나왔을 때 나온 앱들의 디자인보다 현저히 떨어지는 저의 디자인 감각에 놀랐습니다..
    • 하지만 팀원분들께서 어떤 부분에 수정을 하면 좋을지 간단한 조언과 더불어 주말 간에 다시 작업을 해볼 예정입니다.

🔥6주 간의 나의 각오

정말 모든 것이 많이 부족하지만 중요한 것은 꺾이지 않는 마음으로 끝까지 포기하지 않고 6주가 지난 후에 후회 없이 프로젝트를 마무리 하는 것이 저의 각오입니다.

WEEK 2

💡 한주간 느낀 점

벌써 한주가 지났다니 그것이 더 놀라운것 같기도 하다. 이번에는 수직적,수평적 협업 / 페어프로그래밍등 다양한 협업의 경험을 한 주 였습니다.
역시나 안드로이드 팀원분들의 뛰어난 실력에 압도 당했지만 뒤쳐지지는 말고 피해주지 말자는 생각으로 임했습니다.
역시나 스프린트 기간에서도 아키텍처, hilt, flow 등 부족한 개념에 대해서 와장창 무너지는 한주가 되지 않았나 생각하고 이번 프로젝트에 녹여내면서 확실하게 내 것으로 만들자는 생각으로 임했습니다.
이번 주도 많이 부족했지만 계속 정진해나가는 생각으로 다음주도 임할 생각입니다!


💡 1주 간 진행 한 부분

  1. 프로젝트 구조 세팅
  2. 스플래시, 로그인 화면
  3. 마이페이지
  4. 회원가입
  5. Connectivity Manager 설정

💡 1주 간 개인적으로 어려웠거나 개선한 부분

  1. 아키텍쳐
  2. 네트워크 메니저
  3. 소셜 로그인
  4. hilt

🔥다음 주 나의 각오

이번 한 주는 많은 것에 대한 개념 부족으로 인해 구현도 진행을 하였지만 이해에 시간을 굉장히 많이 쏟아 부었던 한주라고 생각합니다.
비록 굉장히 부족하지만 다음주는 이번주보다 훨씬 발전하고 팀원과 소통을 잘하는 개발자가 되기 위해 한걸음 나아가겠습니다.



기술 및 학습 정리

  • 목차
  1. 네트워크 메니저를 통해 사용자 경험 개선하기
  2. 네이버지도 사용자 경험 개선하기



네트워크 메니저를 통해 사용자 경험을 개선시키자


네트워크 설정 공식 레퍼런스

네트워크 상태 읽기  |  Connectivity  |  Android Developers

우리는 앱을 사용하다 보면 네트워크의 연결에 대한 알림을 주는 것을 많이 볼 수 있다.

네트워크 연결 상태를 관리하고 모니터링하여보다 사용자에게 편리함을 제공해주고자 이 부분에 대해서 공략을 해보고자 하였습니다.


네트워크 권한 설정

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

기본적인 이해 (네트워크 순간 상태 가져오기)

val connectivityManager = getSystemService(ConnectivityManager::class.java)

val currentNetwork = connectivityManager.getActiveNetwork()

val caps = connectivityManager.getNetworkCapabilities(currentNetwork)
val linkProperties = connectivityManager.getLinkProperties(currentNetwork)

기본 네트워크의 참조를 가져와 네트워크 정보를 요청하여 알아보려 합니다.

NetworkCapabilities 및 LinkProperties 객체는 네트워크에 관해 시스템이 알고 있는 모든 속성 정보를 제공합니다.


네트워크 이벤트 수신대기

connectivityManager.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network : Network) {
        Log.e(TAG, "The default network is now: " + network)
    }

    override fun onLost(network : Network) {
        Log.e(TAG, "The application no longer has a default network. The last default network was " + network)
    }

    override fun onCapabilitiesChanged(network : Network, networkCapabilities : NetworkCapabilities) {
        Log.e(TAG, "The default network changed capabilities: " + networkCapabilities)
    }

    override fun onLinkPropertiesChanged(network : Network, linkProperties : LinkProperties) {
        Log.e(TAG, "The default network changed link properties: " + linkProperties)
    }
})

onAvailable : 네트워크가 이용이 가능해질 때 호출

onLost : 네트워크가 소실 되었을 때 호출 (디바이스가 모든 네트워크 연결을 잃었을 때 호출)

onCapabilitiesChanged : 네트워크 기능이 변경되었을 때 호출

onLinkPropertiesChanged : 기본 네트워크의 링크 속성이 변경되었을 때 호출

onAvailableonLost를 공식문서의 내용을 살펴보면

네트워크 이용이 가능할 때 와 네트워크가 모든 연결을 잃었을때! 라고 하였지만


네트워크 이슈(문제 사항)


Untitled

연결을 하나 끊었을때나 연결이 되었을 때 호출이 되는 모습을 확인할 수 있었습니다.


네트워크 연결에 대한 연결 정의


  1. Wi-Fi 및 셀룰러 데이터 모두 켜져 있을 때
    • onAvailable 콜백이 호출되어 Wi-Fi 및 셀룰러 데이터가 모두 이용 가능한 상태를 나타냄
  2. Wi-Fi 또는 셀룰러 데이터 중 하나를 끌 때
    • 해당하는 onLost 콜백이 호출될 것. 기존 이용가능하던 네트워크가 소실
  3. Wi-Fi 및 셀룰러 데이터 둘 다 끌 때
    • onLost 콜백이 호출되고 네트워크 전부 소실
  4. Wi-Fi 또는 셀룰러 데이터 중 하나를 다시 켤 때
    • onAvailable 콜백이 호출되어 다시 이용 가능한 상태

즉, 어떤 연결이나 소실이 되었을때 무조건 available, lost가 호출이 된다!

위의 기본적인 내용을 토대로 **NetworkManager** 를 만들어 보았습니다.

class NetworkManager @Inject constructor(private val connectivityManager: ConnectivityManager) {

    private val _isNetworkConnected = MutableStateFlow(isInternetOn())
    val isNetworkConnected: StateFlow<Boolean> = _isNetworkConnected

    private val networkRequest = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .build()

    private fun isInternetOn() : Boolean{
        val network = connectivityManager.activeNetwork
        val connection = connectivityManager.getNetworkCapabilities(network)

        return connection != null &&(
                connection.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                        connection.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
    }

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            _isNetworkConnected.value = isInternetOn()
        }

        override fun onLost(network: Network) {
            _isNetworkConnected.value = isInternetOn()
        }
    }

    fun startNetwork() {
        connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
    }

    fun endNetwork() {
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
}

데이터와 와이파이에 대한 state를 동시에 관찰하여 진행을 해보면 어떨까? 라는 생각으로 inInternetOn() 을 만들어 진행해보았습니다.

default.mp4

그 결과 네트워크에 대한 관찰이 정상적으로 되는 모습을 확인할 수 있습니다!


하지만 최종문제

가끔씩 두가지 모두 state를 관찰하지만 두개가 연결되어있는 상황에 하나의 연결을 끊었을 때 아주 짧은 시간 사이에 네트워크가 변경이 되면서 두가지가 잠시 연결이 끊기는 현상이 발견됩니다.

이 부분에 대해서는 알아보니 금융앱같은 경우 심도있게 다루겠지만 그런게 정말! 중요한 앱이 아니라면 이정도 까지는 아니다가 저의 결론이지만 다음에 시간이나면 이어서 고찰해보는걸로..




네이버지도 사용자 경험 개선하기

네이버 지도에 마커들을 찍는 과정에 대해 안드로이드 팀과의 내부 논의를 통해 계속 발전시킨 과정에 대하여 정리하였습니다.

마커들을 찍는 과정의 발전 순서의 가장 큰 핵심은 **zoom Level고정 값 → LatLng Bounds → zoom Level 유동적인 사용**순으로 진행되었으며 사용자 경험을 고려하여 발전 시켰습니다.


💡 사용자 경험을 위한 진행 과정

  1. Zoom Level 고정적 사용

    1. 마커 간의 평균을 계산하여 중간점으로 이동 진행
    2. 현재 카메라의 위치를 기준으로 가장 가까운 곳으로 이동
  2. LatLng Bounds 사용

    1. LatLng Bounds를 측정하여 zoom 대신 camera Bounds 사용
  3. Zoom Level 고정적 사용X

    1. 현재 카메라를 위치를 고려하여 밀집도와 거리를 계산하여 이동
  4. 최종 본


💡 1-A. 마커간의 평균을 계산하여 중간 점으로 이동 진행

핵심 로직: 음식점들에 대한 위도, 경도의 평균을 구하여 그 곳으로 카메라를 이동하고 줌 레벨을 고정시킵니다.


Zoom Level 낮은 경우

zoom.Level.webm

Zoom Level 높은 경우

zoom.Level.webm

피드백:

  1. Zoom Level이 낮은 경우: 사용자가 확대한 지도가 강제적으로 축소되어 사용자 경험이 감소합니다.
  2. Zoom Level이 높은 경우: 마커들의 평균 중간점으로 인해 일부 마커가 보이지 않을 수 있습니다.

💡 1-B. 현재 카메라의 위치를 기준으로 가장 가까운 곳으로 이동

핵심 로직:

  1. Haversine 거리 공식을 사용하여 현재 사용자 카메라의 위도와 경도를 기준으로 거리를 계산합니다.
  2. 현재 사용자 카메라의 위치와 가장 가까운 거리로 이동합니다.

Zoom Level이 낮은 경우

zoom.webm

Zoom Level이 높은 경우

zoom.webm

피드백:

  • 굉장히 부자연스럽다: 필터 클릭시 강제적인 확대, 축소로 사용자 경험 감소.
  • Zoom Level을 클라이언트가 강제로 고정하는 것은 부자연스러움.

💡 2. LatLng Bounds 측정 → Camera Bounds 사용

핵심 로직:

  1. 각 마커들의 최소, 최대 위,경도 값을 구합니다.
  2. 최소, 최대 위,경도 값을 기준으로 Camera Bounds를 설정합니다.
  3. Bounds에 마커가 찍히는 것을 방지하기 위해 Padding을 설정합니다.
  4. 카메라의 Bounds를 설정합니다.
LatLngbounds.webm

피드백:

  • 카메라의 강제적 확대, 축소로 인한 사용자 경험 감소.
  • 특정 반경 기준으로 모든 음식점을 보여주는 것은 문제.

💡 3. 현재 카메라를 위치를 고려하여 밀집도와 거리를 계산하여 이동

핵심 로직:

  1. Haversine 거리 공식을 사용하여 현재 카메라의 위치와 마커들의 거리를 계산합니다.
  2. 특정 반경 기준 안에 들어온 마커들의 응집도를 계산합니다.
  3. 마커 이동:
    • 응집도가 가장 높은 곳으로 카메라 위치를 업데이트합니다.
    • 특정 반경 기준 안에 마커가 없는 경우 가장 가까운 거리로 이동합니다.

부가 효과:

  1. 애니메이션: 자연스러운 이동 진행.
  2. 특정 반경 기준 안의 마커들만 계산: 시간 복잡도 고려.
default.webm

피드백:

  1. 카메라의 확대, 축소 삭제로 사용자 경험 개선.
  2. 애니메이션 추가로 자연스러운 이동.

💡 최종 개선!

개선점:

  1. 사용자들의 맛집 마커와 근처 음식점 마커에 개별적인 아이콘을 선언합니다.
  2. 근처 음식점 통신을 고려한 거리 계산.
  3. 특정 반경 기준이 아닌 현재 사용자 카메라의 범위를 고려합니다.
  4. Zoom Level → Camera 범위 계산 → 밀집도, 거리 계산 후 카메라 이동합니다.
default.mp4

만족스럽다!👍👍👍👍👍

Clone this wiki locally