-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 메인 화면 ViewModel 구성 및 상태 관리 구현 #24
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` 내의 아이템 순서를 올바르게 변경하는지 확인합니다.
|
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
이 PR은 메인 화면의 ViewModel 구성과 상태 관리를 구현하여 실제 날씨 API 데이터를 활용한 동적 날씨 목록 화면을 제공합니다.
- WeatherRepository 인터페이스와 구현체를 추가하여 API 호출 및 30분 인메모리 캐싱 기능 구현
- WeatherListViewModel을 리팩토링하여 더미 데이터에서 실제 API 데이터 사용으로 전환
- UI 개선: 로딩 상태 표시, 날씨 아이콘 컴포넌트 분리, 도시 일괄 삭제 기능 추가
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
WeatherListViewModelTest.kt | 새로운 ViewModel의 핵심 기능들을 테스트하는 단위 테스트 추가 |
WeatherApi.kt | API 기본 언어 설정을 한국어에서 영어로 변경 |
WeatherListViewModel.kt | 더미 데이터에서 실제 API 호출로 전환, 로딩 상태 및 도시 삭제 기능 구현 |
WeatherListScreen.kt | 로딩 상태 UI 추가, 날씨 아이콘 표시, 도시 객체 기반 선택/삭제 로직 구현 |
WeatherDetailScreen.kt | WeatherIcon 컴포넌트를 별도 파일로 분리 |
Utils.kt | 재사용 가능한 WeatherIcon 컴포넌트 생성 |
RepositoryModule.kt | WeatherRepository DI 바인딩 추가 |
WeatherRepositoryImpl.kt | 날씨 API 호출 및 캐싱 로직을 포함한 구현체 |
WeatherRepository.kt | 날씨 데이터 조회 인터페이스 정의 |
CityStorageRepositoryImpl.kt | 도시 일괄 삭제 기능 및 코드 구조 개선 |
CityStorageRepository.kt | removeCities 메서드 인터페이스 추가 |
WeatherItem.kt | City 객체와 iconCode 필드 추가 |
CachedWeather.kt | 날씨 응답과 타임스탬프를 저장하는 캐시 데이터 클래스 |
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 'displayName()' is defined inside the ViewModel class, making it inaccessible from other classes. Consider moving this to a top-level extension function or a utility class for better reusability.
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.
|
||
data class CachedWeather( | ||
val weather: WeatherResponse, | ||
val timestamp: Long // 저장된 시간 (System.currentTimeMillis()) |
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.
trailing comma 붙여 주세요
val cityName: String, | ||
val temperature: Int, | ||
val weatherType: String, | ||
val iconCode: String, |
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.
trailing comma 붙여 주세요
private val cacheDurationMillis = 30 * 60 * 1000L // 30분 | ||
|
||
override suspend fun getWeather(cityId: Int, latitude: Double, longitude: Double): WeatherResponse { | ||
val lang = 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.
한 줄로 써도 좋지만
if {}
else {}
식으로 brace 붙이는 게 가독성에 유리할 거 같아요
iconCode = weather.weather?.firstOrNull()?.icon.orEmpty() | ||
) | ||
} catch (_: Exception) { | ||
null |
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.
가능한한 null 을 던지는 건 지양하는 것이 좋을 거 같아요. nullable 을 없애고 emptyList 를 보내도 될 거 같습니다
이번 Pull Request는 앱의 날씨 데이터 처리 방식을 크게 개선합니다. 주요 변경 사항에는 인메모리 캐싱 기능이 포함된 날씨 리포지토리 구현, 도시 관리 기능 향상, 날씨 아이콘 및 로딩 상태를 표시하는 UI 업데이트가 포함됩니다. 또한 날씨 목록 화면을 리팩토링하여 실시간 데이터 로딩, 도시 일괄 삭제, 코드 구조 개선을 지원합니다.
날씨 데이터 가져오기 및 캐싱
WeatherRepository
인터페이스와 그 구현체WeatherRepositoryImpl
을 추가하였습니다. 이 구현체는 API로부터 날씨 데이터를 가져오고, 동일 요청을 방지하기 위해 30분 동안 메모리에 캐싱합니다. 언어 설정은 디바이스 로케일에 따라 자동으로 선택됩니다. (WeatherRepository.kt
[1]WeatherRepositoryImpl.kt
[2])CachedWeather
데이터 클래스를 추가하였습니다. (CachedWeather.kt
app/src/main/java/com/ben/simpleweather/data/CachedWeather.ktR1-R8)도시 관리 기능 개선
CityStorageRepository
와 그 구현체를 리팩토링하여 도시 일괄 삭제를 지원하고, 저장 로직을 유지보수에 유리하게 개선하였습니다. (CityStorageRepository.kt
[1]CityStorageRepositoryImpl.kt
[2])City
객체를 기준으로 삭제 기능을 구현하여, UI에서 보다 안정적인 도시 삭제 기능을 제공하도록 했습니다. (WeatherListScreen.kt
[1] [2] [3] [4];WeatherListViewModel.kt
[5])UI 및 UX 개선
WeatherListScreen.kt
app/src/main/java/com/ben/simpleweather/features/main/WeatherListScreen.ktL115-R154)WeatherIcon
컴포저블을 추가하였고, 관련 화면에서 이를 사용하도록 리팩토링했습니다. (Utils.kt
[1]WeatherDetailScreen.kt
[2] [3]WeatherListScreen.kt
[4])의존성 주입 및 모듈 업데이트
WeatherRepository
를 Dagger/Hilt DI 모듈에 등록하여 생명주기 관리를 적절히 수행하도록 했습니다. (RepositoryModule.kt
[1] [2])기타 개선 사항
WeatherApi
에서 기본 언어 파라미터를 영어로 변경하고, 런타임에서 로케일에 맞게 언어를 선택하도록 변경했습니다. (WeatherApi.kt
app/src/main/java/com/ben/simpleweather/network/WeatherApi.ktL15-R23)WeatherItem
에City
객체와iconCode
필드를 추가하여 UI에서 더 많은 정보를 보여줄 수 있도록 개선했습니다. (WeatherItem.kt
app/src/main/java/com/ben/simpleweather/data/WeatherItem.ktR4-R8)closes #9