{behavior}/ticket-{number}-{something}
- e.g. :
feature/ticket-007-data-module
feat : {something}
: 새로운 기능을 추가했을 때fix : {something}
: 기능 중 버그를 수정했을 때design : {something}
: 디자인 일부를 변경했을 때refactor : {something}
: 코드를 재정비 하였을 때chore : {something}
: 빌드 관련 업무를 수정하거나 패키지 매니저 수정했을 때docs : {something}
: README와 같은 문서를 변경했을 때test : {something}
: 누락된 테스트 코드를 추가했을 때
- UI와 관련된 파일들이 저장되어 있으며 Activity, DialogFragment, WebViewBridge, ViewModel 등으로 구성되어 있습니다.
- MVVM 패턴을 사용합니다.
- 비즈니스 영역과 데이터 영역에서 사용되는 로직을 제공하는 패키지로 interface 및 model로 구성되어 있습니다.
- repository
- Data layer에 접근하여 데이터를 처리할 수 있는 인터페이스를 정의하고 있습니다.
- model
- 실제 UI에 적용되는 비즈니스 데이터를 담고 있습니다.
- Repository
- RepositoryImpl에는 도메인에서 정의한 repository 인터페이스가 구현되어있습니다.
- Datasource는 도메인에서 데이터에 접근할 수 있도록 해줍니다.
- DB
- RoomDatabase, Dao, Entity가 구성되어 있습니다.
- mapper
- Entity와 Model 사이 변환을 위한 확장 함수가 구현되어 있습니다.
- DI
- Hilt 라이브러리를 사용해 의존성 주입을 위한 Module이 구성되어있습니다.
- RoomModule, GsonModule, RepositoryModule
- Hilt
- Room
- Coroutine, Flow
- 맡은 부분
- Base Architecture 작성
- 기여한 점
- RoomDatabase 연동
- Repository 패턴 적용
- DI를 위한 Module 구성
- 아쉬운 점
- 이번 프로젝트를 진행하며 팀원들이 기능 구현에 어려움을 겪는 걸 봤는데, Base Architecture 구성을 맡음으로써 기능 구현에 도움이 많이 못 된 것이 아쉽습니다.
- 맡은 부분
- 인트로 화면
- 기여한 점
Splash Screen
구성- 3초 후 메인화면 이동
- 네트워크 연결 상태 체크
- 아쉬운 점
- Splash Screen Icon에 Animation 기능을 추가하지 못했습니다.
Splash Screen | 위치 설정 권한 거부 | 네트워크 연결 상태 체크 |
---|---|---|
1.mp4 |
2.mp4 |
3.mp4 |
private fun initData() {
thread(true) {
for (i in 1..3) {
Thread.sleep(1000)
}
isReady = true
}
}
- 별도의 작업은 수행하지 않고 3초간의 딜레이를 주고 이후 작업을 진행합니다.
private fun initSplashScreen() {
initData()
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
return if (isReady) {
content.viewTreeObserver.removeOnPreDrawListener(this)
if (!isConnection()) {
Snackbar.make(
binding.root,
R.string.networkError,
Snackbar.LENGTH_SHORT
).show()
}
checkPermission()
true
} else {
false
}
}
}
)
}
addOnPreDrawListener
를 통하여Splash Screen
이 그려지는 것에 대한 결과를 받습니다.onPreDraw
는Splash Screen
이 보이는 동안 계속해서 호출되며,isReady
가 true일 때 true를 반환하면서 해당 스크린을 지우도록 처리하였습니다.isReady
는initData
에서 3초 뒤에 true로 변경하도록 설정해두었습니다. 그로인해,Splash Screen
이 3초 후에 제거됩니다.
- 맡은 부분
- 히스토리 표시하기
- 기여한 점
- WebView와 Google Map JavaScript API 연동
- WebViewBridge 개발 및 Android Component와 연동
- Map 구성을 위해 HTML, JavaScript, CSS 작성
- 아쉬운 점
- WebView 최적화를 진행하지 못해 성능이 많이 아쉽습니다.
- 현재 ViewPort에 해당하는 History만 가져올 수 있도록 구현하지 못했습니다.
- 현재 위치를 표시하는 버튼을 별도로 구현하지 못했습니다.
맵 연동 | 마커 추가 | 히스토리 불러오기 |
---|---|---|
KakaoTalk_20220922_204143776.mp4 |
KakaoTalk_20220922_215354659.mp4 |
KakaoTalk_20220922_215715785.mp4 |
- assets 파일에 html 파일을 구성하여 apk에 포함되도록 구성했습니다.
- 웹뷰에서 호출하는 html으로, 비동기적으로 JavaScript에서 구현한 initMap()를 호출하여 Map을 구성합니다.
javascript
- zoom: 20 으로 건물이 보이는 정도로 거리를 유지했습니다.
- disableDefaultUI: true 로 지정하여 기본적으로 구성하는 버튼을 사용하지 않도록 만들었습니다.
LocalDataSource
- Room DB를 flow를 이용하여 변경이 발생했을 시, 데이터를 방출할 수 있는 구조로 설계했습니다.
WebViewBridge
- Activity의 메서드를 호출해야하는 부분을 interface로 만들고 힐트로 부터 주입받도록 구현했습니다.
- Coroutine을 관리하기 위해 SupervisorJob과 CoroutineExceptionHandler 기능을 갖는 CoroutineContext를 만들어서 사용했습니다.
- WebViewBridge를 통해 collect로부터 전달받은 history 정보를 Map에 전달하며 Coroutine에서 동작하도록 만들었습니다.
JavaScriptUrlUtil
- Util 클래스를 통해서 webView 요청을 위한 Url을 생성합니다.
- javascript를 호출하기 위한 url로, error 처리를 위해서 try/catch 문으로 작성하였습니다.
JavaScript
- url을 통해 실행될 JavaScript 파일입니다.
- bridge를 통한 데이터 전달은 직렬화를 통해서만 가능하기 때문에 javascript에서 다시 역직렬화하여 객체값으로 구성했습니다.
- room에 저장된 위/경도에 따라 마커를 추가하도록 합니다.
- 맡은 부분
- 설정 다이얼로그
- 히스토리 다이얼로그 (미완)
- 기여한 점
BaseDialog
구성WorkManager
를 통해SharedPreferences
에 유저가 설정한 저장 간격을 저장WorkManager
에서Hilt
를 사용할 수 있도록 구성
- 아쉬운 점
WorkManager
를 좀 더 제대로 알았다면 클린한 코드가 완성됐을 것 같다.- 협업 컨벤션을 명확히 지정하고 프로젝트에 임했다면 더 좋았을 것 같다.
- 취소 버튼을 누르거나 다이얼로그 외부 영역을 클릭하면 다이얼로그가 사라집니다.
- 확인 버튼을 누르면 저장 간격을 내부 데이터베이스에 저장하고, 간격에 따라 현 위치를 내부 데이터베이스에 저장하는 동작을 시작합니다.
binding.confirm.setOnClickListener {
val interval = binding.etMinute.text.toString().toLong()
val workRequest =
PeriodicWorkRequestBuilder<CurrentLocationWorker>(interval, TimeUnit.MINUTES)
.build()
workManager.enqueue(workRequest)
viewModel.updateStorageInterval(interval)
dismiss()
}
- 저장 버튼을 눌렀을 때 동작하는 로직입니다.
workManager
를viewModel
에 넣을지도 고민했지만,viewModel
에서는 안드로이드 의존을 최대한 피하는게 좋겠다고 판단하여DialogFragment
에서 사용하였습니다.
@HiltWorker
class CurrentLocationWorker @AssistedInject constructor(
@Assisted private val context: Context,
@Assisted parameters: WorkerParameters,
private val locationRepository: LocationRepository
) : CoroutineWorker(context, parameters) {
override suspend fun doWork(): Result {
lateinit var result: Result
coroutineScope {
withContext(Dispatchers.IO) {
if (checkPermission()) {
val manager: LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val currentLocation: android.location.Location? =
manager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val latitude = currentLocation?.latitude
val longitude = currentLocation?.longitude
val currentTimestamp = Timestamp(System.currentTimeMillis()).time
val location = if (latitude != null && longitude != null) {
Location(0, latitude.toFloat(), longitude.toFloat(), currentTimestamp)
} else {
Location.EMPTY
}
insertLocation(location)
result = Result.success()
} else {
result = Result.failure()
}
}
}
return result
}
private fun checkPermission(): Boolean {
return ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
private suspend fun insertLocation(location: Location) {
locationRepository.insertLocation(location)
}
}
- 설정 간격에 따라 내부 데이터베이스에 현 위치를 저장하는 로직입니다.
- 안드로이드 기기에서 GPS를 통해 현 위치를 추적합니다.
js
,webView
로 앱을 구성한만큼js
의 로직으로 현 위치를 불러오고 싶었으나, 시간 상 여유가 부족하여 더 나은 로직을 발견하지 못했습니다.
- 히스토리 다이얼로그에서는 내부 데이터베이스에 저장된 위치 정보를 확인할 수 있습니다. (시간 상의 이유로 날짜별 리스트 출력 미구현)