-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 상세 화면 ViewModel 구현 및 날씨 데이터 처리 #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
날씨 아이콘 코드를 사용하여 이미지를 표시하는 `WeatherIcon` Composable 함수를 `Utils.kt` 파일에 추가했습니다. 이 함수는 아이콘 URL을 생성하고 `AsyncImage`를 사용하여 이미지를 로드합니다. 아이콘 크기와 Modifier를 선택적으로 지정할 수 있습니다.
`WeatherApi.kt` 파일에서 현재 날씨 및 예보 API 호출 시 사용되는 언어(`lang`) 파라미터의 기본값을 "kr" (한국어)에서 "en" (영어)으로 수정했습니다.
`WeatherRepository` 인터페이스와 그 구현체인 `WeatherRepositoryImpl`을 추가했습니다. `WeatherRepositoryImpl`은 API로부터 가져온 날씨 정보를 30분 동안 캐싱하여, 동일한 도시 ID에 대한 반복적인 API 호출을 줄이도록 구현되었습니다. 현재 시스템 언어 설정을 감지하여 한국어인 경우 'kr'로, 그 외에는 'en'으로 API 호출 시 언어 파라미터를 설정합니다.
`CityStorageRepository` 인터페이스 및 `CityStorageRepositoryImpl` 구현체에 여러 도시를 한 번에 삭제하는 `removeCities` 함수를 추가했습니다. 또한, 도시 목록을 저장하는 로직을 `saveCities`라는 private 함수로 분리하여 중복 코드를 제거하고 가독성을 향상시켰습니다. 예외 처리 시 사용되지 않는 변수명을 `_`로 변경했습니다.
날씨 응답(`WeatherResponse`)과 타임스탬프를 포함하는 `CachedWeather` 데이터 클래스를 추가했습니다. 이 클래스는 날씨 데이터를 캐시하고 저장 시간을 추적하는 데 사용됩니다.
`WeatherItem.kt` 파일의 `WeatherItem` 데이터 클래스에 `city` (City 객체)와 `iconCode` (String) 필드를 추가했습니다. 이를 통해 날씨 항목에 도시 정보와 날씨 아이콘 코드를 포함할 수 있게 됩니다.
`WeatherListViewModel`을 수정하여 저장된 도시들의 현재 날씨 정보를 OpenWeatherMap API를 통해 가져오도록 구현했습니다. 또한, 선택된 도시들을 `CityStorageRepository`에서 삭제하고 화면 목록에서도 제거하는 기능을 추가했습니다. 로딩 상태를 나타내는 `isLoading` StateFlow와 현재 시스템 언어 설정에 따라 도시 이름을 표시하는 `displayName()` 확장 함수도 추가되었습니다.
- `WeatherListScreen`에 날씨 아이콘을 표시하도록 `WeatherIcon` Composable을 추가했습니다. - 로딩 상태를 표시하기 위해 `CircularProgressIndicator`를 추가하고, 도시 목록이 비어 있을 때의 UI를 개선했습니다. - 도시를 선택하여 삭제하는 기능과 드래그 앤 드롭으로 순서 변경 기능을 구현했습니다. - `WeatherDetailScreen`에서 중복되던 `WeatherIcon` Composable을 제거하고 공통으로 사용하도록 수정했습니다. - 도시 목록 화면으로 돌아올 때 저장된 도시의 날씨 정보를 다시 불러오도록 `LaunchedEffect`를 사용했습니다.
Hilt를 사용하여 WeatherRepository 인터페이스와 그 구현체인 WeatherRepositoryImpl을 바인딩하는 코드를 RepositoryModule.kt에 추가했습니다. 이를 통해 의존성 주입을 통해 WeatherRepository를 사용할 수 있게 됩니다.
`WeatherListViewModel`의 주요 기능들을 검증하는 `WeatherListViewModelTest.kt` 파일을 추가했습니다. 이 테스트는 `CityStorageRepository` 및 `WeatherRepository`의 Mock 객체를 사용하여 ViewModel의 로직을 검증합니다. 주요 테스트 시나리오: - `loadWeatherForSavedCities`: 저장된 도시 목록에 대한 날씨 정보를 불러와 `weatherList` LiveData를 올바르게 업데이트하는지 확인합니다. - `deleteSelected`: 선택된 도시들을 `weatherList`에서 제거하고, `CityStorageRepository`의 `removeCities` 함수를 호출하여 저장소에서도 삭제하는지 확인합니다. - `moveItem`: `weatherList` 내의 아이템 순서를 올바르게 변경하는지 확인합니다.
`SimpleWeatherNavHost.kt` 및 `WeatherDetailScreen.kt` 파일을 수정하여 날씨 상세 화면으로 전달하는 네비게이션 인자를 기존의 `cityName`(String)에서 `cityid`(Int)로 변경했습니다. 이를 통해 도시 식별 방식을 개선하고, 잠재적인 도시 이름 중복 문제를 방지합니다.
`WeatherDetailScreen.kt` 파일에서 다음과 같은 변경 사항을 적용했습니다: - `LaunchedEffect`의 키를 `cityName`에서 `cityid`로 변경하여 도시 ID를 기반으로 날씨 정보를 로드하도록 수정했습니다. - `TopAppBar`의 `title`을 빈 `Text`로 설정하여 화면 상단의 도시 이름 표시를 제거했습니다.
`WeatherRepository` 인터페이스 및 구현체(`WeatherRepositoryImpl`)에 날씨 예보 정보를 가져오는 `getForecast` 함수를 추가했습니다. 또한, API 요청 시 사용되는 언어 설정을 `getLang` 함수로 분리하여 코드 중복을 줄이고 가독성을 향상시켰습니다.
`CityStorageRepository` 인터페이스 및 `CityStorageRepositoryImpl` 클래스에 저장된 도시 목록에서 특정 ID를 가진 도시를 찾는 `getCityById` 함수를 추가했습니다.
`CachedWeather.kt` 파일에서 `timestamp` 필드의 주석을 제거했습니다.
`SimpleWeatherNavHost.kt` 파일에서 상세 화면으로 이동하는 네비게이션 로직을 수정했습니다. `cityid` 파라미터의 타입을 `NavType.IntType`으로 명시적으로 지정하여 타입 안정성을 강화했습니다.
`WeatherDetailViewModel`에 `WeatherRepository`와 `CityStorageRepository`를 주입하여 실제 날씨 데이터와 도시 정보를 가져오도록 수정했습니다. `loadWeather` 함수는 `cityId`를 인자로 받아 해당 도시의 현재 날씨와 예보를 API를 통해 가져오고, 이를 `WeatherDetail` 및 `ForecastItem`으로 변환하여 UI 상태를 업데이트합니다. 또한, 현재 도시 이름을 `_cityName` StateFlow를 통해 관리하고 화면에 표시하도록 `WeatherDetailScreen`을 수정했습니다. 시간 표시는 시스템 기본 시간대에 맞춰 "HH:mm" 형식으로 포맷됩니다. 주요 변경사항: - `WeatherDetailViewModel`: - API를 통해 날씨 및 예보 데이터 로드 - `ForecastResponse` 및 `WeatherResponse`를 화면 표시용 데이터 클래스로 변환하는 확장 함수 추가 - 현재 도시 이름을 관리하는 `_cityName` StateFlow 추가 - `WeatherDetailScreen`: - `cityId`를 `LaunchedEffect`의 key로 사용하여 도시 변경 시 데이터 재로드 - TopAppBar에 현재 도시 이름 표시
`WeatherDetailViewModel.kt` 파일을 수정하여 날씨 상세 정보에 강수 확률을 표시하도록 변경했습니다. `getWeatherAndForecast` 함수 내에서 예보 데이터(`forecast.list`)의 첫 번째 항목의 `pop` 값을 가져와 강수 확률을 계산하고, 이 값을 `WeatherResponse.toWeatherDetail` 함수에 전달하여 `WeatherDetail` 객체에 포함시키도록 수정했습니다.
`WeatherDetailViewModel.kt` 파일에서 사용되지 않는 `android.util.Log` import 문을 제거하고, 예외 처리 시 사용하지 않는 변수명을 `_`로 변경했습니다.
`WeatherDetailScreen.kt` 파일에서 사용되지 않는 `coil.compose.AsyncImage`와 `androidx.compose.ui.unit.Dp` import 문을 제거했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
날씨 앱의 구조를 대대적으로 리팩토링하여 Repository 패턴을 도입하고 데이터 흐름을 개선한 PR입니다.
- 새로운
WeatherRepository
도입으로 날씨 데이터 처리 및 캐싱 기능 구현 - 네비게이션 방식을 city name 기반에서 city ID 기반으로 변경
- UI 컴포넌트 분리 및 테스트 코드 추가로 코드 품질 향상
Reviewed Changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
WeatherRepositoryImpl.kt | 날씨 API 호출 및 캐싱 로직을 담당하는 새로운 Repository 구현체 |
WeatherListViewModel.kt | Repository 패턴을 활용한 ViewModel 리팩토링 및 실제 날씨 데이터 처리 |
WeatherDetailViewModel.kt | city ID 기반 상세 데이터 로드 및 DTO-UI 모델 매핑 로직 추가 |
WeatherListScreen.kt | 로딩 상태 처리 및 새로운 데이터 구조 반영 |
SimpleWeatherNavHost.kt | 네비게이션을 city name에서 city ID로 변경 |
Utils.kt | WeatherIcon 컴포저블을 재사용 가능한 유틸리티로 분리 |
WeatherListViewModelTest.kt | 새로운 ViewModel 로직에 대한 단위 테스트 추가 |
import com.ben.simpleweather.data.remote.dto.ForecastResponse | ||
import com.ben.simpleweather.data.remote.dto.WeatherResponse | ||
import com.ben.simpleweather.network.WeatherApi | ||
import jakarta.inject.Inject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect import statement. Should use 'javax.inject.Inject' instead of 'jakarta.inject.Inject' for Dagger/Hilt dependency injection in Android projects.
import jakarta.inject.Inject | |
import javax.inject.Inject |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jakarta 는 어디서 왔을까요?
fun City.displayName(): String { | ||
return if (Locale.getDefault().language == "ko") { | ||
nameKo | ||
} else { | ||
name | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extension function should be moved outside of the class or made private. Public extension functions defined within a ViewModel class can lead to confusion about their scope and usage.
fun City.displayName(): String { | |
return if (Locale.getDefault().language == "ko") { | |
nameKo | |
} else { | |
name | |
} | |
} | |
} | |
fun City.displayName(): String { | |
return if (Locale.getDefault().language == "ko") { | |
nameKo | |
} else { | |
name | |
} |
Copilot uses AI. Check for mistakes.
|
} catch (_: Exception) { | ||
_uiState.value = WeatherUiState.Error("날씨 데이터를 불러오는 데 실패했습니다.") | ||
} | ||
} | ||
} | ||
|
||
fun ForecastResponse.toForecastList(): List<ForecastItem> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 익스텐션들은 익스텐션의 원본 클래스 아래에 정의해도 좋을 거 같아요
뷰모델 안에서 익스텐션을 정의했기 때문에 뷰모델 밖에서는 익스텐션에 접근할 수 없습니다
title = { Text(cityName) }, | ||
title = { | ||
Text( | ||
text = cityName ?: "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cityName이 null 일 경우는 드물겠지만, 만에 하나 그렇다면 오류가 났다는 걸 명시적으로 보여 줘도 좋을 거 같아요
import com.ben.simpleweather.data.remote.dto.ForecastResponse | ||
import com.ben.simpleweather.data.remote.dto.WeatherResponse | ||
import com.ben.simpleweather.network.WeatherApi | ||
import jakarta.inject.Inject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jakarta 는 어디서 왔을까요?
return api.getWeatherForecast(lat, lon, lang = lang) | ||
} | ||
|
||
private fun getLang(): String = if (Locale.getDefault().language == "ko") "kr" else "en" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brace 를 붙여서 가독성 있게 해 주세요
이 풀 리퀘스트는 날씨 앱의 구조를 대대적으로 리팩토링하고 기능을 업그레이드합니다
새로운 Repository 레이어를 도입하여 날씨 데이터를 처리하고, 도시 관리 기능을 개선하며, 날씨 리스트 및 상세 UI의 데이터 흐름을 간결하게 리팩토링했습니다. 또한 날씨 데이터를 캐싱하고, 상세 화면 네비게이션에 city name 대신 city ID를 사용하도록 변경하였습니다.
📦 Repository 및 데이터 레이어 개선
WeatherRepository
와 그 구현체WeatherRepositoryImpl
을 새로 추가하여 날씨 및 예보 데이터를 가져오고 캐싱하는 역할을 수행합니다.CachedWeather
데이터 클래스를 추가하여 캐싱 구조를 정의했습니다.CityStorageRepository
에 다음 기능 추가:🧭 네비게이션 및 데이터 전달 구조 개선
city name
문자열이 아닌city ID
를 사용하도록 네비게이션 구조를 전면 수정했습니다.💡 UI 및 ViewModel 업데이트
WeatherDetailViewModel
에서 새로운 Repository를 사용하도록 리팩토링하고, DTO → UI 모델 매핑 및 도시명 로컬라이징 처리 로직을 추가했습니다.WeatherListScreen
에서 다음 사항을 개선:WeatherItem
데이터 클래스에City
객체와 아이콘 코드 필드 추가🧹 코드 정리 및 유틸리티 분리
WeatherIcon
컴포저블을 재사용 가능하도록 별도 유틸리티 파일로 분리여러 파일에서 불필요한 import 및 코드 정리 수행
closes [Issue #10] 상세 화면 ViewModel – 현재 및 예보 데이터 처리 #10