From 424e95b4615f399bed364438d4b79d41a2d8d5aa Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 11:33:46 +0100 Subject: [PATCH 1/7] add leak canary --- app/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f042d9b..6c07e01 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -195,6 +195,9 @@ dependencies { // Chucker debugImplementation(libs.chucker.debug) releaseImplementation(libs.chucker.release) + + // Memory Leak Detection + debugImplementation(libs.leakcanary) } kapt { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80b6f4a..38eda52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,6 +39,7 @@ play-services-location = "21.1.0" retrofit = "2.9.0" truth = "1.4.2" turbine = "1.0.0" +leakcanary = "3.0-alpha-1" [libraries] activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } @@ -88,6 +89,7 @@ playservices-location = { module = "com.google.android.gms:play-services-locatio retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } truth = { module = "com.google.truth:truth", version.ref = "truth" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } +leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } [plugins] com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } From 649ab8104b3a1d118e7335e016e87810a9987641 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 12:18:46 +0100 Subject: [PATCH 2/7] fix bug on crashlytics ,load after language is set --- README.md | 2 +- .../java/com/github/odaridavid/weatherapp/MainViewModel.kt | 2 +- .../weatherapp/data/settings/DefaultSettingsRepository.kt | 4 ++-- .../odaridavid/weatherapp/data/weather/remote/Mappers.kt | 7 +++++-- .../com/github/odaridavid/weatherapp/ui/AppNavGraph.kt | 2 -- .../github/odaridavid/weatherapp/ui/home/HomeViewModel.kt | 1 + 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f7703f2..2e5ac3f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ In your `local.properties` you will need to add your Open Weather API key and co ```properties OPEN_WEATHER_API_KEY = YOUR KEY OPEN_WEATHER_BASE_URL=https://api.openweathermap.org -OPEN_WEATHER_ICONS_URL= http://openweathermap.org/img/wn/ +OPEN_WEATHER_ICONS_URL= https://openweathermap.org/img/wn/ ``` Check for one under [`Api Keys`](https://home.openweathermap.org/api_keys) diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt b/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt index 0925705..0c38e3b 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt @@ -51,7 +51,7 @@ class MainViewModel @Inject constructor( data class MainViewState( val isPermissionGranted: Boolean = false, val isLocationSettingEnabled: Boolean = false, - val defaultLocation: DefaultLocation? = null + val defaultLocation: DefaultLocation? = DefaultLocation(longitude = 0.0, latitude = 0.0) ) sealed class MainViewIntent { diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/data/settings/DefaultSettingsRepository.kt b/app/src/main/java/com/github/odaridavid/weatherapp/data/settings/DefaultSettingsRepository.kt index 0ab7dd7..7401a5f 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/data/settings/DefaultSettingsRepository.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/data/settings/DefaultSettingsRepository.kt @@ -43,9 +43,9 @@ class DefaultSettingsRepository @Inject constructor( "Version : ${BuildConfig.VERSION_NAME}-${BuildConfig.BUILD_TYPE}" override fun getAvailableLanguages(): List = - SupportedLanguage.values().map { it.languageName } + SupportedLanguage.entries.map { it.languageName } - override fun getAvailableUnits(): List = Units.values().map { it.value } + override fun getAvailableUnits(): List = Units.entries.map { it.value } override suspend fun setDefaultLocation(defaultLocation: DefaultLocation) { set(key = PREF_LAT_LNG, value = "${defaultLocation.latitude}/${defaultLocation.longitude}") diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/data/weather/remote/Mappers.kt b/app/src/main/java/com/github/odaridavid/weatherapp/data/weather/remote/Mappers.kt index 0caf9e1..01f233c 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/data/weather/remote/Mappers.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/data/weather/remote/Mappers.kt @@ -87,8 +87,8 @@ private fun getDate(utcInMillis: Long, formatPattern: String): String { fun mapResponseCodeToThrowable(code: Int): Throwable = when (code) { HttpURLConnection.HTTP_UNAUTHORIZED -> UnauthorizedException("Unauthorized access : $code") - in 400..499 -> ClientException("Client error : $code") - in 500..600 -> ServerException("Server error : $code") + in CLIENT_ERRORS -> ClientException("Client error : $code") + in SERVER_ERRORS -> ServerException("Server error : $code") else -> GenericException("Generic error : $code") } @@ -102,3 +102,6 @@ fun mapThrowableToErrorType(throwable: Throwable): ErrorType { } return errorType } + +private val SERVER_ERRORS = 500..600 +private val CLIENT_ERRORS = 400..499 \ No newline at end of file diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/AppNavGraph.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/AppNavGraph.kt index ca9e932..97f8e1d 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/AppNavGraph.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/AppNavGraph.kt @@ -26,8 +26,6 @@ fun WeatherAppScreensConfig( .collectAsState() .value - homeViewModel.processIntent(HomeScreenIntent.LoadWeatherData) - HomeScreen( state = state, onSettingClicked = { navController.navigate(Destinations.SETTINGS.route) }, diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt index f28fe6f..e6970f1 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/home/HomeViewModel.kt @@ -41,6 +41,7 @@ class HomeViewModel @Inject constructor( defaultLocation = defaultLocation ) } + processIntent(HomeScreenIntent.LoadWeatherData) } } } From 324d124a4cfbb5739c0ccbe1cff1302291750008 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 12:28:20 +0100 Subject: [PATCH 3/7] fix tests --- .../weatherapp/HomeViewModelIntegrationTest.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt index f7e3623..dadd4d3 100644 --- a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt +++ b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt @@ -1,11 +1,14 @@ package com.github.odaridavid.weatherapp import app.cash.turbine.test +import com.github.odaridavid.weatherapp.core.Result import com.github.odaridavid.weatherapp.core.api.Logger import com.github.odaridavid.weatherapp.core.api.SettingsRepository import com.github.odaridavid.weatherapp.core.api.WeatherRepository +import com.github.odaridavid.weatherapp.core.model.CurrentWeather import com.github.odaridavid.weatherapp.core.model.DefaultLocation import com.github.odaridavid.weatherapp.core.model.TimeFormat +import com.github.odaridavid.weatherapp.core.model.Weather import com.github.odaridavid.weatherapp.data.weather.DefaultWeatherRepository import com.github.odaridavid.weatherapp.data.weather.remote.DefaultRemoteWeatherDataSource import com.github.odaridavid.weatherapp.data.weather.remote.OpenWeatherService @@ -150,7 +153,9 @@ class HomeViewModelIntegrationTest { @Test fun `when we receive a city name, the state is updated with it`() = runTest { - val weatherRepository = mockk() + val weatherRepository = mockk(){ + coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success(fakeSuccessMappedWeatherResponse) + } val viewModel = createViewModel( weatherRepository = weatherRepository @@ -163,8 +168,8 @@ class HomeViewModelIntegrationTest { ), locationName = "Paradise", language = "English", - weather = null, - isLoading = true, + weather = fakeSuccessMappedWeatherResponse, + isLoading = false, errorMessageId = null ) From 940bd5bc5f48ec98f26bc50279a674136316d5c2 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 12:45:43 +0100 Subject: [PATCH 4/7] log failure callback --- .../com/github/odaridavid/weatherapp/MainViewModel.kt | 9 ++++++++- .../com/github/odaridavid/weatherapp/ui/MainActivity.kt | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt b/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt index 0c38e3b..f712365 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/MainViewModel.kt @@ -2,6 +2,7 @@ package com.github.odaridavid.weatherapp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.odaridavid.weatherapp.core.api.Logger import com.github.odaridavid.weatherapp.core.api.SettingsRepository import com.github.odaridavid.weatherapp.core.model.DefaultLocation import dagger.hilt.android.lifecycle.HiltViewModel @@ -13,7 +14,8 @@ import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( - private val settingsRepository: SettingsRepository + private val settingsRepository: SettingsRepository, + private val logger: Logger ) : ViewModel() { private val _state = MutableStateFlow(MainViewState()) @@ -37,6 +39,9 @@ class MainViewModel @Inject constructor( } setState { copy(defaultLocation = defaultLocation) } } + is MainViewIntent.LogException -> { + logger.logException(mainViewIntent.throwable) + } } } @@ -62,4 +67,6 @@ sealed class MainViewIntent { data class ReceiveLocation(val latitude: Double, val longitude: Double) : MainViewIntent() + data class LogException(val throwable: Throwable) : MainViewIntent() + } diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt index b212f27..4d771d9 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt @@ -98,6 +98,8 @@ class MainActivity : ComponentActivity() { ) ) } + }.addOnFailureListener { exception -> + mainViewModel.processIntent(MainViewIntent.LogException(throwable = exception)) } WeatherAppScreensConfig(navController = rememberNavController()) } From f6289bdea5e2e3ab48126c21b581956a9a821da8 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 13:03:11 +0100 Subject: [PATCH 5/7] fix tests --- .../odaridavid/weatherapp/HomeViewModelIntegrationTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt index dadd4d3..3f07b12 100644 --- a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt +++ b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt @@ -128,7 +128,9 @@ class HomeViewModelIntegrationTest { @Test fun `when we init the screen, then update the state`() = runBlocking { - val weatherRepository = createWeatherRepository() + val weatherRepository = mockk(){ + coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success(fakeSuccessMappedWeatherResponse) + } val viewModel = createViewModel(weatherRepository = weatherRepository) @@ -139,8 +141,8 @@ class HomeViewModelIntegrationTest { ), locationName = "-", language = "English", - weather = null, - isLoading = true, + weather = fakeSuccessMappedWeatherResponse, + isLoading = false, errorMessageId = null ) From 9efc9a4613788aa3d72f48510937ae7dd51bd0df Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 13:07:50 +0100 Subject: [PATCH 6/7] ktlint cleanup --- .../odaridavid/weatherapp/HomeViewModelIntegrationTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt index 3f07b12..dc6bfa8 100644 --- a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt +++ b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt @@ -5,10 +5,8 @@ import com.github.odaridavid.weatherapp.core.Result import com.github.odaridavid.weatherapp.core.api.Logger import com.github.odaridavid.weatherapp.core.api.SettingsRepository import com.github.odaridavid.weatherapp.core.api.WeatherRepository -import com.github.odaridavid.weatherapp.core.model.CurrentWeather import com.github.odaridavid.weatherapp.core.model.DefaultLocation import com.github.odaridavid.weatherapp.core.model.TimeFormat -import com.github.odaridavid.weatherapp.core.model.Weather import com.github.odaridavid.weatherapp.data.weather.DefaultWeatherRepository import com.github.odaridavid.weatherapp.data.weather.remote.DefaultRemoteWeatherDataSource import com.github.odaridavid.weatherapp.data.weather.remote.OpenWeatherService From 502d7270ae8e6d23f54f5af59b34bbb60d89a0f9 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Mon, 4 Mar 2024 13:08:31 +0100 Subject: [PATCH 7/7] ktlint fixes --- .../weatherapp/HomeViewModelIntegrationTest.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt index dc6bfa8..50967ce 100644 --- a/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt +++ b/app/src/test/java/com/github/odaridavid/weatherapp/HomeViewModelIntegrationTest.kt @@ -126,8 +126,10 @@ class HomeViewModelIntegrationTest { @Test fun `when we init the screen, then update the state`() = runBlocking { - val weatherRepository = mockk(){ - coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success(fakeSuccessMappedWeatherResponse) + val weatherRepository = mockk() { + coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success( + fakeSuccessMappedWeatherResponse + ) } val viewModel = createViewModel(weatherRepository = weatherRepository) @@ -153,8 +155,10 @@ class HomeViewModelIntegrationTest { @Test fun `when we receive a city name, the state is updated with it`() = runTest { - val weatherRepository = mockk(){ - coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success(fakeSuccessMappedWeatherResponse) + val weatherRepository = mockk() { + coEvery { fetchWeatherData(any(), any(), any()) } returns Result.Success( + fakeSuccessMappedWeatherResponse + ) } val viewModel = createViewModel(