diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b317bc7f..e4223f55 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -26,6 +26,11 @@ jobs:
steps:
- name: Checkout the code
uses: actions/checkout@v2
+ - name: Setup environment
+ env:
+ GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
+ run: |
+ echo "${GOOGLE_SERVICES_JSON}" | base64 --decode > app/google-services.json
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index da056a24..a1b7dfb4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,6 +15,11 @@ jobs:
- run: |
echo "${{ secrets.KEYSTORE_FILE_BASE64 }}" > keystore.asc
gpg -d --passphrase "${{ secrets.KEYSTORE_FILE_DECRYPT_PASSWORD }}" --batch keystore.asc > .keystore
+ - name: Setup environment
+ env:
+ GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
+ run: |
+ echo "${GOOGLE_SERVICES_JSON}" | base64 --decode > app/google-services.json
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
@@ -26,10 +31,6 @@ jobs:
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
run: |
./gradlew assembleGithubRelease
- sudo apt-get -y install tree
- tree app/build/outputs
- ls -lah app/build/
- ls -lah
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
diff --git a/README.md b/README.md
index c44d0f9f..fec59ba0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
[![github-ci](https://github.com/vadret/android/workflows/V%C3%A4dret%20Android%20App/badge.svg)](https://github.com/vadret/android/actions)
+[![github-releases](https://img.shields.io/github/v/release/sphrak/vadret)](https://github.com/vadret/android/releases/)
[![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/vadret/android/blob/master/LICENSE)
[![CodeFactor](https://www.codefactor.io/repository/github/vadret/android/badge)](https://www.codefactor.io/repository/github/vadret/android)
@@ -6,7 +7,7 @@
![Vädret](https://raw.githubusercontent.com/vadret/android/master/assets/logo.png)
# Vädret
-A simple weather app that uses the Swedish weather service [SMHI](https://opendata-download-metfcst.smhi.se/) to fetch weather data, and [Krisinformation](https://www.krisinformation.se/en) for emergency information from Swedish authorities. Built with MVI/MVVM in mind on top of RxJava2, written entirely in Kotlin. Icons used in this project can be found [here](https://github.com/vadret/assets). The data is licensed under [Creative commons Erkännande 4.0 SE](https://www.smhi.se/klimatdata/oppna-data/information-om-oppna-data/villkor-for-anvandning-1.30622).
+A simple weather app that uses the Swedish weather service [SMHI](https://opendata-download-metfcst.smhi.se/) to fetch weather data, and [Krisinformation](https://www.krisinformation.se/en) for emergency information from Swedish authorities. MVI written entirely in Kotlin. Icons used in this project can be found [here](https://github.com/vadret/assets). The data is licensed under [Creative commons Erkännande 4.0 SE](https://www.smhi.se/klimatdata/oppna-data/information-om-oppna-data/villkor-for-anvandning-1.30622).
![Weather](https://raw.githubusercontent.com/vadret/android/master/assets/weather.png)
![Warning](https://raw.githubusercontent.com/vadret/android/master/assets/warning.png)
diff --git a/app/build.gradle b/app/build.gradle
index 46f731d4..3d051ae8 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,9 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlinx-serialization'
+apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.firebase.crashlytics'
def static loadFromEnvironment(String key) {
return System.getenv(key)
@@ -39,8 +42,8 @@ android {
applicationId "fi.kroon.vadret"
minSdkVersion 21
targetSdkVersion 30
- versionCode 26
- versionName "1.2.10"
+ versionCode 27
+ versionName "2.0.0"
vectorDrawables.useSupportLibrary = true
flavorDimensions "default"
testInstrumentationRunner "androidx.top.runner.AndroidJUnitRunner"
@@ -96,6 +99,9 @@ android {
productFlavors {
google {
signingConfig signingConfigs.release
+ firebaseCrashlytics {
+ mappingFileUploadEnabled true
+ }
}
fdroid {
signingConfig fdroid.signingConfig
@@ -131,18 +137,18 @@ dependencies {
def CONSTRAINT_LAYOUT_VERSION = "2.0.2"
def CORBIND_VERSION = "1.4.0"
def CORE_KTX_VERSION = "1.3.2"
- def COROUTINES_VERSION = "1.4.0-M1"
- def CRASHLYTICS_VERSION = "2.10.1"
- def DAGGER_VERSION = "2.29.1"
+ def COROUTINES_VERSION = "1.4.2"
+ def DAGGER_VERSION = "2.30.1"
def EITHER_VERSION = "1.2.0"
def FRAGMENT_VERSION = "1.3.0-beta01"
def JUNIT_VERSION = "4.13"
def KOTLIN_STDLIB_VERSION = "1.4.10"
- def KTLINT_VERSION = "0.39.0"
+ def KOTLINX_SERIALIZATION = "1.0.1"
+ def KTLINT_VERSION = "0.40.0"
def MATERIAL_VERSION = "1.2.1"
def MOCKITO_CORE_VERSION = "3.1.0"
def MOSHI_VERSION = "1.9.2"
- def NAVIGATION_VERSION = "2.3.1"
+ def NAVIGATION_VERSION = "2.3.2"
def OKHTTP_VERSION = "4.2.2"
def OKIO_VERSION = "2.6.0"
def OSMDROID_VERSION = "6.1.2"
@@ -158,6 +164,7 @@ dependencies {
def THREETEN_ABP_VERSION = "1.2.1"
def THREETEN_BP_VERSION = "1.4.0"
def TIMBER_VERSION = "4.7.1"
+ def LIFECYCLE_VERSION = "2.2.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
@@ -171,6 +178,11 @@ dependencies {
implementation "androidx.preference:preference-ktx:${PREFERENCE_VERSION}"
implementation "com.google.android.material:material:${MATERIAL_VERSION}"
+ // Lifecycle
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION"
+ kapt "androidx.lifecycle:lifecycle-compiler:$LIFECYCLE_VERSION"
+
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${COROUTINES_VERSION}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${COROUTINES_VERSION}"
@@ -196,6 +208,7 @@ dependencies {
implementation "com.squareup.retrofit2:adapter-rxjava2:${RETROFIT_VERSION}"
implementation "com.squareup.retrofit2:converter-moshi:${RETROFIT_VERSION}"
implementation "com.squareup.retrofit2:retrofit:${RETROFIT_VERSION}"
+ implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
// Database
implementation "androidx.room:room-runtime:${ROOM_VERSION}"
@@ -208,6 +221,7 @@ dependencies {
implementation "com.squareup.moshi:moshi:${MOSHI_VERSION}"
implementation "com.squareup.retrofit2:converter-moshi:${RETROFIT_VERSION}"
kapt "com.squareup.moshi:moshi-kotlin-codegen:${MOSHI_VERSION}"
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:${KOTLINX_SERIALIZATION}"
// Dependency Injection
implementation "com.google.dagger:dagger:${DAGGER_VERSION}"
@@ -229,9 +243,8 @@ dependencies {
// Debugging, Testing, Linting, Analytics
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.5"
- googleImplementation("com.crashlytics.sdk.android:crashlytics:${CRASHLYTICS_VERSION}@aar") {
- transitive = true
- }
+ googleImplementation platform('com.google.firebase:firebase-bom:26.0.0')
+ googleImplementation 'com.google.firebase:firebase-crashlytics-ktx'
implementation "com.jakewharton.timber:timber:${TIMBER_VERSION}"
ktlint "com.pinterest:ktlint:${KTLINT_VERSION}"
testImplementation "junit:junit:${JUNIT_VERSION}"
diff --git a/app/src/googleRelease/java/fi/kroon/vadret/VadretApplication.kt b/app/src/googleRelease/java/fi/kroon/vadret/VadretApplication.kt
index 9d9ec9db..883dc8cc 100644
--- a/app/src/googleRelease/java/fi/kroon/vadret/VadretApplication.kt
+++ b/app/src/googleRelease/java/fi/kroon/vadret/VadretApplication.kt
@@ -1,9 +1,7 @@
package fi.kroon.vadret
import android.content.Context
-import com.crashlytics.android.Crashlytics
-import com.crashlytics.android.core.CrashlyticsCore
-import io.fabric.sdk.android.Fabric
+import com.google.firebase.crashlytics.FirebaseCrashlytics
class VadretApplication : BaseApplication() {
@@ -19,9 +17,6 @@ class VadretApplication : BaseApplication() {
}
private fun initCrashlytics() {
- val crashlyticsKit = Crashlytics.Builder()
- .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
- .build()
- Fabric.with(this, crashlyticsKit)
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
}
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 15129e51..1e4b92b9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,7 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepository.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepository.kt
index ad746811..a5763972 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepository.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepository.kt
@@ -11,19 +11,13 @@ import fi.kroon.vadret.data.weatherforecast.model.Weather
import fi.kroon.vadret.data.weatherforecast.model.WeatherOut
import fi.kroon.vadret.data.weatherforecast.net.WeatherForecastNetDataSource
import fi.kroon.vadret.util.HTTP_200_OK
-import fi.kroon.vadret.util.HTTP_204_NO_CONTENT
-import fi.kroon.vadret.util.HTTP_400_BAD_REQUEST
-import fi.kroon.vadret.util.HTTP_403_FORBIDDEN
-import fi.kroon.vadret.util.HTTP_404_NOT_FOUND
-import fi.kroon.vadret.util.HTTP_500_INTERNAL_SERVER_ERROR
-import fi.kroon.vadret.util.HTTP_503_SERVICE_UNAVAILABLE
-import fi.kroon.vadret.util.HTTP_504_GATEWAY_TIMEOUT
import fi.kroon.vadret.util.NetworkHandler
import fi.kroon.vadret.util.extension.asLeft
+import fi.kroon.vadret.util.extension.asRight
import fi.kroon.vadret.util.extension.toCoordinate
import io.github.sphrak.either.Either
-import io.reactivex.Single
import retrofit2.Response
+import timber.log.Timber
import javax.inject.Inject
class WeatherForecastRepository @Inject constructor(
@@ -33,9 +27,9 @@ class WeatherForecastRepository @Inject constructor(
private val exceptionHandler: ExceptionHandler
) : IErrorHandler by errorHandler, IExceptionHandler by exceptionHandler {
- operator fun invoke(request: WeatherOut): Single> =
- when (networkHandler.isConnected) {
- true ->
+ suspend operator fun invoke(request: WeatherOut): Either =
+ try {
+ if (networkHandler.isConnected) {
weatherForecastNetDataSource
.get()
.getWeatherForecast(
@@ -43,22 +37,20 @@ class WeatherForecastRepository @Inject constructor(
request.version,
request.longitude.toCoordinate(),
request.latitude.toCoordinate()
- ).map { response: Response ->
- when (response.code()) {
- HTTP_200_OK -> Either.Right(response.body()!!)
- HTTP_204_NO_CONTENT -> WeatherForecastFailure.NoWeatherAvailable.asLeft()
- HTTP_403_FORBIDDEN -> Failure.HttpForbidden403.asLeft()
- HTTP_404_NOT_FOUND -> WeatherForecastFailure.NoWeatherAvailableForThisLocation.asLeft()
- HTTP_400_BAD_REQUEST -> Failure.HttpBadRequest400.asLeft()
- HTTP_500_INTERNAL_SERVER_ERROR -> Failure.HttpInternalServerError500.asLeft()
- HTTP_503_SERVICE_UNAVAILABLE -> Failure.HttpServiceUnavailable503.asLeft()
- HTTP_504_GATEWAY_TIMEOUT -> Failure.HttpGatewayTimeout504.asLeft()
- else -> WeatherForecastFailure.NoWeatherAvailable.asLeft()
+ ).let { response: Response ->
+ if (response.code() == HTTP_200_OK) {
+ response.body()?.asRight() ?: WeatherForecastFailure.NoWeatherAvailable.asLeft()
+ } else {
+ WeatherForecastFailure.NoWeatherAvailable.asLeft()
}
}
- false -> getNetworkOfflineError()
- }.onErrorReturn {
- exceptionHandler(it)
- .asLeft()
+ } else {
+ Failure
+ .NetworkOfflineError("error: network offline or not available")
+ .asLeft()
+ }
+ } catch (exception: Exception) {
+ Timber.e(exception)
+ Failure.NetworkError().asLeft()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastCacheDataSource.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastCacheDataSource.kt
deleted file mode 100644
index 76fb3642..00000000
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastCacheDataSource.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package fi.kroon.vadret.data.weatherforecast.cache
-
-import androidx.collection.LruCache
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import fi.kroon.vadret.util.extension.asLeft
-import fi.kroon.vadret.util.extension.asRight
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class WeatherForecastCacheDataSource @Inject constructor(
- private val diskCache: WeatherForecastDiskCache,
- private val memoryCache: LruCache
-) {
-
- /**
- * In-memory Cache
- */
- fun getMemoryCache(cacheKey: String): Single> =
- Single.fromCallable {
- memoryCache
- .snapshot()
- .getValue(cacheKey)
- .asRight() as Either
- }.onErrorReturn {
- Failure
- .MemoryCacheLruReadFailure
- .asLeft()
- }
-
- fun updateMemoryCache(cacheKey: String, weather: Weather): Single> {
- memoryCache.put(cacheKey, weather)
- return Single.just(
- weather.asRight() as Either
- ).onErrorReturn {
- Failure
- .MemoryCacheLruWriteFailure
- .asLeft()
- }
- }
-
- fun clearMemoryCache(): Single> = Single.fromCallable {
- memoryCache
- .evictAll()
- Unit.asRight() as Either
- }.doOnError { failure ->
- Timber.e("Memory cache eviction failed: $failure")
- }.onErrorReturn {
- Failure
- .MemoryCacheEvictionFailure
- .asLeft()
- }
-
- /**
- * Disk IO Cache
- */
- fun getDiskCache(cacheKey: String): Single> = diskCache
- .read(cacheKey)
-
- fun updateDiskCache(weather: Weather, cacheKey: String): Single> =
- diskCache.put(cacheKey, weather)
-
- fun clearDiskCache(cacheKey: String): Single> = Single.fromCallable {
- diskCache.remove(cacheKey)
- Unit.asRight() as Either
- }.doOnError { failure ->
- Timber.e("Disk cache eviction failed: $failure")
- }.onErrorReturn {
- Failure
- .DiskCacheEvictionFailure
- .asLeft()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastDiskCache.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastDiskCache.kt
deleted file mode 100644
index a5a62f29..00000000
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/cache/WeatherForecastDiskCache.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package fi.kroon.vadret.data.weatherforecast.cache
-
-import fi.kroon.vadret.data.common.BaseCache
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import fi.kroon.vadret.util.extension.asLeft
-import fi.kroon.vadret.util.extension.asRight
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import okhttp3.internal.cache.DiskLruCache
-import okio.BufferedSink
-import okio.Sink
-import okio.buffer
-import timber.log.Timber
-import javax.inject.Inject
-
-class WeatherForecastDiskCache @Inject constructor(
- private val cache: DiskLruCache
-) : BaseCache() {
-
- init {
- cache.initialize()
- }
-
- private companion object {
- const val INDEX = 0
- }
-
- fun put(cacheKey: String, weather: Weather): Single> =
- Single.fromCallable {
- val editor: DiskLruCache.Editor? = cache.edit(cacheKey)
- val sink: Sink? = editor?.newSink(INDEX)
- val bufferedSink: BufferedSink? = sink?.buffer()
- val byteArray: ByteArray = serializerObject(weather)
-
- bufferedSink.use { _ ->
- bufferedSink?.write(byteArray)
- }
- editor?.commit()
-
- weather.asRight() as Either
- }.doOnError {
- Timber.e("Disk cache insert failed: $it")
- }.onErrorReturn {
- Failure
- .DiskCacheLruWriteFailure
- .asLeft()
- }
-
- fun read(cacheKey: String): Single> = Single.fromCallable {
- val snapshot: DiskLruCache.Snapshot = cache[cacheKey]!!
- val byteArray: ByteArray
- byteArray = snapshot.getSource(INDEX)
- .buffer()
- .readByteArray()
-
- deserializeBytes(byteArray)
- .asRight() as Either
- }.onErrorReturn {
- Failure
- .DiskCacheLruReadFailure
- .asLeft()
- }
-
- fun remove(cacheKey: String): Single> = Single.fromCallable {
- cache
- .remove(cacheKey)
- .asRight() as Either
- }.onErrorReturn {
- Failure
- .DiskCacheEvictionFailure
- .asLeft()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/exception/WeatherForecastFailure.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/exception/WeatherForecastFailure.kt
index 335d3d99..4b11386c 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/exception/WeatherForecastFailure.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/exception/WeatherForecastFailure.kt
@@ -5,5 +5,4 @@ import fi.kroon.vadret.data.failure.Failure
class WeatherForecastFailure {
object NoWeatherAvailable : Failure.FeatureFailure()
object NoWeatherAvailableForThisLocation : Failure.FeatureFailure()
- object CachingWeatherForecastDataFailed : Failure.FeatureFailure()
}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Geometry.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Geometry.kt
index 02e2d612..caa8445d 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Geometry.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Geometry.kt
@@ -1,14 +1,12 @@
package fi.kroon.vadret.data.weatherforecast.model
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import java.io.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
-@JsonClass(generateAdapter = true)
+@Serializable
data class Geometry(
- @Json(name = "type")
+ @SerialName(value = "type")
val type: String = "Point",
- @Json(name = "coordinates")
+ @SerialName(value = "coordinates")
val coordinates: List>
-
-) : Serializable
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Parameter.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Parameter.kt
index 971fb238..39096015 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Parameter.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Parameter.kt
@@ -1,24 +1,23 @@
package fi.kroon.vadret.data.weatherforecast.model
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import java.io.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
-@JsonClass(generateAdapter = true)
+@Serializable
data class Parameter(
- @Json(name = "name")
+ @SerialName(value = "name")
val name: String,
- @Json(name = "levelType")
+ @SerialName(value = "levelType")
val levelType: String,
- @Json(name = "level")
- val level: String,
+ @SerialName(value = "level")
+ val level: Int,
- @Json(name = "unit")
+ @SerialName(value = "unit")
val unit: String,
- @Json(name = "values")
- val values: List
-) : Serializable
\ No newline at end of file
+ @SerialName(value = "values")
+ val values: List = emptyList()
+)
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/TimeSerie.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/TimeSerie.kt
index 380aad90..06b7b578 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/TimeSerie.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/TimeSerie.kt
@@ -1,13 +1,12 @@
package fi.kroon.vadret.data.weatherforecast.model
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import java.io.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
-@JsonClass(generateAdapter = true)
+@Serializable
data class TimeSerie(
- @Json(name = "validTime")
+ @SerialName(value = "validTime")
val validTime: String,
- @Json(name = "parameters")
- val parameters: List
-) : Serializable
\ No newline at end of file
+ @SerialName(value = "parameters")
+ val parameters: List = emptyList()
+)
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Weather.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Weather.kt
index 48fb9930..387d27c3 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Weather.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/Weather.kt
@@ -1,25 +1,21 @@
package fi.kroon.vadret.data.weatherforecast.model
-import android.os.Parcelable
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import kotlinx.android.parcel.Parcelize
-import java.io.Serializable
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
-@Parcelize
-@JsonClass(generateAdapter = true)
+@Serializable
data class Weather(
- @Json(name = "approvedTime")
+ @SerialName(value = "approvedTime")
val approvedTime: String,
- @Json(name = "referenceTime")
+ @SerialName(value = "referenceTime")
val referenceTime: String,
- @Json(name = "geometry")
+ @SerialName(value = "geometry")
val geometry: Geometry,
- @Json(name = "timeSeries")
- val timeSeries: List? = emptyList()
+ @SerialName(value = "timeSeries")
+ val timeSeries: List = emptyList()
-) : Serializable, Parcelable
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/net/WeatherForecastNetDataSource.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/net/WeatherForecastNetDataSource.kt
index 987e3d97..2da7e916 100644
--- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/net/WeatherForecastNetDataSource.kt
+++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/net/WeatherForecastNetDataSource.kt
@@ -1,7 +1,6 @@
package fi.kroon.vadret.data.weatherforecast.net
import fi.kroon.vadret.data.weatherforecast.model.Weather
-import io.reactivex.Single
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
@@ -12,10 +11,10 @@ interface WeatherForecastNetDataSource {
* Provide lat/lon for 10 day forecast
*/
@GET("/api/category/{category}/version/{version}/geotype/point/lon/{longitude}/lat/{latitude}/data.json")
- fun getWeatherForecast(
+ suspend fun getWeatherForecast(
@Path("category") category: String,
@Path("version") version: Int,
@Path("longitude") longitude: Double,
@Path("latitude") latitude: Double
- ): Single>
+ ): Response
}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastDiskCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastDiskCacheTask.kt
deleted file mode 100644
index 9ae1bb5e..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastDiskCacheTask.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class ClearWeatherForecastDiskCacheTask @Inject constructor(
- private val cacheRepo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(cacheKey: String): Single> =
- cacheRepo
- .clearDiskCache(cacheKey)
- .doOnError {
- Timber.e("DisplayError clearing disk cache: $it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastMemoryCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastMemoryCacheTask.kt
deleted file mode 100644
index e5652327..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/ClearWeatherForecastMemoryCacheTask.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class ClearWeatherForecastMemoryCacheTask @Inject constructor(
- private val cacheRepo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(): Single> =
- cacheRepo
- .clearMemoryCache()
- .doOnError {
- Timber.e("DisplayError clearing memory cache: $it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastDiskCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastDiskCacheTask.kt
deleted file mode 100644
index c099721a..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastDiskCacheTask.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class GetWeatherForecastDiskCacheTask @Inject constructor(
- private val repo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(cacheKey: String): Single> =
- repo
- .getDiskCache(cacheKey)
- .doOnError {
- Timber.e("$it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastMemoryCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastMemoryCacheTask.kt
deleted file mode 100644
index 6fecbb4e..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastMemoryCacheTask.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class GetWeatherForecastMemoryCacheTask @Inject constructor(
- private val cacheRepo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(cacheKey: String): Single> =
- cacheRepo
- .getMemoryCache(cacheKey)
- .doOnError {
- Timber.e("$it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastService.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastService.kt
index e25aae56..5e273f82 100644
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastService.kt
+++ b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastService.kt
@@ -8,15 +8,12 @@ import fi.kroon.vadret.data.weatherforecast.model.WeatherOut
import fi.kroon.vadret.domain.IService
import fi.kroon.vadret.presentation.weatherforecast.WeatherForecastMapper
import fi.kroon.vadret.presentation.weatherforecast.model.IWeatherForecastModel
-import fi.kroon.vadret.util.FIVE_MINUTES_IN_MILLIS
-import fi.kroon.vadret.util.extension.asSingle
-import fi.kroon.vadret.util.extension.flatMapSingle
import io.github.sphrak.either.Either
import io.github.sphrak.either.flatMap
import io.github.sphrak.either.map
-import io.reactivex.Single
-import io.reactivex.rxkotlin.zipWith
-import timber.log.Timber
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.rx2.await
+import kotlinx.coroutines.withContext
import javax.inject.Inject
class GetWeatherForecastService @Inject constructor(
@@ -24,11 +21,7 @@ class GetWeatherForecastService @Inject constructor(
private val getLocationAutomaticTask: GetLocationAutomaticTask,
private val getReverseLocalityNameTask: GetReverseLocalityNameTask,
private val getAppLocationModeTask: GetAppLocationModeTask,
- private val getLocationManualTask: GetLocationManualTask,
- private val setWeatherForecastDiskCacheTask: SetWeatherForecastDiskCacheTask,
- private val setWeatherForecastMemoryCacheTask: SetWeatherForecastMemoryCacheTask,
- private val getWeatherForecastMemoryCacheTask: GetWeatherForecastMemoryCacheTask,
- private val getWeatherForecastDiskCacheTask: GetWeatherForecastDiskCacheTask
+ private val getLocationManualTask: GetLocationManualTask
) : IService {
data class Data(
@@ -42,63 +35,56 @@ class GetWeatherForecastService @Inject constructor(
val location: Location? = null
)
- /**
- * [cacheKey] Must conform to regex [a-z0-9_-]{1,120}
- */
- private companion object {
- const val cacheKey: String = "weather_forecast_app_cache_key_"
- }
-
/**
* [timeStamp] -- Timestamp issued at time of request, used to control whether cache or network
* should be used.
* [forceNet] -- Forces a network request regardless of value in timeStamp.
*/
- operator fun invoke(timeStamp: Long, forceNet: Boolean): Single> =
- Single.just(Data(timeStamp = timeStamp, forceNet = forceNet))
- .flatMap(::getLocationMode)
- .flatMap(::getGpsLocationOrStoredLocation)
- .flatMap(::getWeatherForecastList)
- .flatMap(::doReverseNominatimLookupOrReturn)
- .map(::transform)
+ suspend operator fun invoke(timeStamp: Long, forceNet: Boolean): Either =
+ withContext(Dispatchers.Default) {
+ getEmptyData(timeStamp = timeStamp, forceNet = forceNet)
+ .getLocationMode()
+ .getGpsLocationOrStoredLocation()
+ .getWeatherForecastList()
+ .doReverseNominatimLookupOrReturn()
+ .toWeatherForecastMapper()
+ }
+
+ private fun getEmptyData(timeStamp: Long, forceNet: Boolean): Data = Data(timeStamp = timeStamp, forceNet = forceNet)
/**
* Determine if location should be derived from GPS or local storage.
*/
- private fun getLocationMode(data: Data): Single> =
+ private suspend fun Data.getLocationMode(): Either =
getAppLocationModeTask()
- .map { either: Either ->
- either.map { locationMode ->
- data.copy(locationMode = locationMode)
- }
+ .await()
+ .map { locationMode: Boolean ->
+ this.copy(locationMode = locationMode)
}
- private fun getGpsLocationOrStoredLocation(either: Either): Single> =
- either.flatMapSingle { data: Data ->
+ private suspend fun Either.getGpsLocationOrStoredLocation(): Either =
+ this.flatMap { data: Data ->
when (data.locationMode) {
false -> getLocationManual(data)
- true ->
- getLocationAutomatic(data)
- .map(::mapLocationEntityToWeatherOut)
+ true -> mapLocationEntityToWeatherOut(getLocationAutomatic(data))
}
}
- private fun getLocationAutomatic(data: Data): Single> =
- getLocationAutomaticTask().map { either: Either ->
- either.map { location: Location ->
+ private suspend fun getLocationAutomatic(data: Data): Either =
+ getLocationAutomaticTask()
+ .await()
+ .map { location: Location ->
data.copy(location = location)
}
- }
- private fun getLocationManual(data: Data): Single> =
+ private suspend fun getLocationManual(data: Data): Either =
getLocationManualTask()
- .map { either: Either ->
- either.map { weatherOut: WeatherOut ->
- data.copy(
- weatherOut = weatherOut,
- localityName = weatherOut.localityName!!
- )
- }
+ .await()
+ .map { weatherOut: WeatherOut ->
+ data.copy(
+ weatherOut = weatherOut,
+ localityName = weatherOut.localityName!!
+ )
}
/**
@@ -113,124 +99,53 @@ class GetWeatherForecastService @Inject constructor(
data.copy(weatherOut = weatherOut)
}
- private fun getWeatherForecastList(either: Either): Single> =
- either.flatMapSingle { data: Data ->
+ private suspend fun Either.getWeatherForecastList(): Either =
+ this.flatMap { data: Data ->
getWeather(data)
- .map { either: Either ->
- either.map { dataIn: Data ->
- dataIn.copy(
- timeStamp = currentTimeMillis
- )
- }
+ .map { dataIn: Data ->
+ dataIn.copy(
+ timeStamp = currentTimeMillis
+ )
}
}
- private fun doReverseNominatimLookupOrReturn(either: Either): Single> =
- either.flatMapSingle { data: Data ->
+ private suspend fun Either.doReverseNominatimLookupOrReturn(): Either =
+ this.flatMap { data: Data ->
when (data.locationMode) {
- true -> doReverseNominatimLookup(either)
+ true -> doReverseNominatimLookup(this)
false -> {
- either.asSingle()
+ this
}
}
}
- private fun doReverseNominatimLookup(either: Either): Single> =
- either.flatMapSingle { data: Data ->
+ private suspend fun doReverseNominatimLookup(either: Either): Either =
+ either.flatMap { data: Data ->
val nominatimReverseOut = NominatimReverseOut(
latitude = data.weatherOut!!.latitude,
longitude = data.weatherOut.longitude
)
- getReverseLocalityNameTask(nominatimReverseOut).map { result ->
- result.map { localityName: String? ->
+ getReverseLocalityNameTask(nominatimReverseOut)
+ .await()
+ .map { localityName: String? ->
localityName?.let {
data.copy(localityName = localityName)
} ?: data
}
- }
}
- private fun getWeather(data: Data): Single> =
- with(data) {
- when {
- forceNet || (currentTimeMillis > (timeStamp + FIVE_MINUTES_IN_MILLIS)) -> {
- Timber.d("DATA: $data")
- getWeatherForecastTask(data.weatherOut!!)
- .map { either: Either ->
- either.map { weather: Weather ->
- data.copy(weather = weather)
- }
- }.flatMap { either: Either ->
- updateCache(either)
- }
- }
- else -> {
- Single.merge(
- getWeatherForecastMemoryCacheTask(cacheKey)
- .map { either: Either ->
- either.map { weather ->
- data.copy(weather = weather)
- }
- },
- getWeatherForecastDiskCacheTask(cacheKey)
- .map { either: Either ->
- either.map { weather ->
- data.copy(weather = weather)
- }
- }
- ).filter { result ->
- result.either(
- {
- false
- },
- { data ->
- Timber.d("Fetched from cache: ${data.weather}")
- data
- .weather!!
- .timeSeries!!
- .isNotEmpty()
- }
- )
- }.take(1)
- .switchIfEmpty(
- getWeatherForecastTask(data.weatherOut!!)
- .map { either: Either ->
- Timber.d("Cache was empty, fetching weather from network.")
- either.map { weather: Weather ->
- data.copy(weather = weather)
- }
- }.flatMap { data ->
- updateCache(data)
- }.toFlowable()
- ).singleOrError()
- }
+ private suspend fun getWeather(data: Data): Either {
+ return getWeatherForecastTask(data.weatherOut!!)
+ .map { weather: Weather ->
+ data.copy(weather = weather)
}
- }
-
- private fun updateCache(either: Either): Single> =
- either.flatMapSingle { data: Data ->
- setWeatherForecastMemoryCacheTask(cacheKey, data.weather!!)
- .zipWith(setWeatherForecastDiskCacheTask(cacheKey, data.weather))
- .map { pair: Pair,
- Either> ->
- Timber.i("Updating cache")
- val (
- firstEither: Either,
- secondEither: Either
- ) = pair
- firstEither.flatMap { _: Weather ->
- secondEither.map { _: Weather ->
- data
- }
- }
- }
- }
+ }
- private fun transform(either: Either): Either =
- either.map { data: Data ->
+ private fun Either.toWeatherForecastMapper(): Either =
+ this.map { data: Data ->
val locationEntity = Location(data.weatherOut!!.latitude, data.weatherOut.longitude)
val baseWeatherForecastModelList: List = WeatherForecastMapper(
- data.weather!!.timeSeries!!,
+ data.weather!!.timeSeries,
locationEntity
)
data.copy(weatherForecastModelList = baseWeatherForecastModelList)
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastTask.kt
index 7335377b..8dc9483d 100644
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastTask.kt
+++ b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/GetWeatherForecastTask.kt
@@ -5,12 +5,11 @@ import fi.kroon.vadret.data.weatherforecast.WeatherForecastRepository
import fi.kroon.vadret.data.weatherforecast.model.Weather
import fi.kroon.vadret.data.weatherforecast.model.WeatherOut
import io.github.sphrak.either.Either
-import io.reactivex.Single
import javax.inject.Inject
class GetWeatherForecastTask @Inject constructor(
- private val repo: WeatherForecastRepository
+ private val weatherForecastRepository: WeatherForecastRepository
) {
- operator fun invoke(request: WeatherOut): Single> =
- repo(request)
+ suspend operator fun invoke(request: WeatherOut): Either =
+ weatherForecastRepository(request = request)
}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastDiskCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastDiskCacheTask.kt
deleted file mode 100644
index 662aac9f..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastDiskCacheTask.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class SetWeatherForecastDiskCacheTask @Inject constructor(
- private val repo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(cacheKey: String, weather: Weather): Single> =
- repo
- .updateDiskCache(weather, cacheKey)
- .doOnError {
- Timber.e("$it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastMemoryCacheTask.kt b/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastMemoryCacheTask.kt
deleted file mode 100644
index a71d4f46..00000000
--- a/app/src/main/java/fi/kroon/vadret/domain/weatherforecast/SetWeatherForecastMemoryCacheTask.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package fi.kroon.vadret.domain.weatherforecast
-
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.cache.WeatherForecastCacheDataSource
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import timber.log.Timber
-import javax.inject.Inject
-
-class SetWeatherForecastMemoryCacheTask @Inject constructor(
- private val cacheRepo: WeatherForecastCacheDataSource
-) {
- operator fun invoke(cacheKey: String, weather: Weather): Single> =
- cacheRepo
- .updateMemoryCache(cacheKey, weather)
- .doOnError {
- Timber.e("$it")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt
index 65077c78..3f2a2ae6 100644
--- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt
+++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt
@@ -141,13 +141,9 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) {
super.onDestroyView()
Timber.d("ON DESTROY VIEW -- WEATHER FORECAST")
- weatherForecastRecyclerView.apply {
- adapter = null
- }
-
- autoCompleteRecyclerView.apply {
- adapter = null
- }
+ weatherForecastRecyclerView.adapter = null
+ autoCompleteRecyclerView.adapter = null
+ weatherForecastSearchView.setOnQueryTextListener(null)
hideActionBarLocalityName()
}
@@ -226,13 +222,13 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) {
.offer(
WeatherForecastView.Event.OnSearchButtonToggled
)
- }.launchIn(lifecycleScope)
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
weatherForecastRefresh
.refreshes()
.map {
eventChannel.offer(WeatherForecastView.Event.OnSwipedToRefresh)
- }.launchIn(lifecycleScope)
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
weatherForecastSearchView
.queryTextChangeEvents()
@@ -257,7 +253,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) {
)
}
}
- }.launchIn(lifecycleScope)
+ }.launchIn(viewLifecycleOwner.lifecycleScope)
eventChannel.offer(
WeatherForecastView
diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastMapper.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastMapper.kt
index c0b34022..32cb4b54 100644
--- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastMapper.kt
+++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastMapper.kt
@@ -8,12 +8,10 @@ import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastDateIte
import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastHeadlineModel
import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastItemModel
import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastSplashItemModel
-import fi.kroon.vadret.util.MPS_TO_KMPH_FACTOR
-import fi.kroon.vadret.util.WINDCHILL_FORMULA_MAXIMUM
-import fi.kroon.vadret.util.WINDCHILL_FORMULA_MINIMUM
import fi.kroon.vadret.util.common.SunsetUtil
+import fi.kroon.vadret.util.common.WindChill
import fi.kroon.vadret.util.extension.parseToLocalDate
-import fi.kroon.vadret.util.extension.toWindChill
+import org.threeten.bp.Instant
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.ZoneId
import java.util.Calendar
@@ -139,10 +137,6 @@ object WeatherForecastMapper {
}
weatherDescription = weatherIcon
- if (temperature < WINDCHILL_FORMULA_MAXIMUM) {
- feelsLikeTemperature = if (windSpeed * MPS_TO_KMPH_FACTOR > WINDCHILL_FORMULA_MINIMUM) temperature.toWindChill(windSpeed) else null
- }
-
return WeatherForecastSplashItemModel(
sunriseDateTime = sunriseDateTime,
sunsetDateTime = sunsetDateTime,
@@ -150,7 +144,7 @@ object WeatherForecastMapper {
temperature = temperature,
windSpeed = windSpeed,
windDirection = windDirection,
- feelsLikeTemperature = feelsLikeTemperature,
+ feelsLikeTemperature = WindChill.calculate(temperature, windSpeed),
weatherIcon = weatherIcon,
weatherDescription = weatherDescription,
precipitationCode = precipitationCode,
@@ -160,7 +154,7 @@ object WeatherForecastMapper {
private fun getWeatherForecastItemModel(timeSerie: TimeSerie): WeatherForecastItemModel {
var temperature: Double = 0.0
- val time: String = OffsetDateTime.parse(timeSerie.validTime).toLocalTime().toString()
+ val time: String = Instant.parse(timeSerie.validTime).atZone(ZoneId.systemDefault()).toLocalTime().toString()
var feelsLikeTemperature: String? = null
var windSpeed: Double = 0.0
val precipitationType: Int = 0
@@ -177,14 +171,10 @@ object WeatherForecastMapper {
weatherDescription = weatherIcon
- if (temperature < WINDCHILL_FORMULA_MAXIMUM) {
- feelsLikeTemperature = if (windSpeed * MPS_TO_KMPH_FACTOR > WINDCHILL_FORMULA_MINIMUM) temperature.toWindChill(windSpeed) else null
- }
-
return WeatherForecastItemModel(
temperature = temperature,
time = time,
- feelsLikeTemperature = feelsLikeTemperature,
+ feelsLikeTemperature = WindChill.calculate(temperature, windSpeed),
precipitationType = precipitationType,
windSpeed = windSpeed,
weatherIcon = weatherIcon,
diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastViewModel.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastViewModel.kt
index 8fbae962..1514eb7a 100644
--- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastViewModel.kt
+++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastViewModel.kt
@@ -47,9 +47,9 @@ class WeatherForecastViewModel @Inject constructor(
reduce(event)
}
- private suspend fun reduce(event: WeatherForecastView.Event): WeatherForecastView.State {
+ private suspend fun reduce(event: WeatherForecastView.Event): WeatherForecastView.State = withContext(Dispatchers.IO) {
Timber.d("event: $event")
- return when (event) {
+ when (event) {
is WeatherForecastView.Event.OnViewInitialised -> onViewInitialisedEvent(event.stateParcel)
WeatherForecastView.Event.OnLocationPermissionGranted -> onLocationPermissionGrantedEvent()
WeatherForecastView.Event.OnLocationPermissionDenied -> onLocationPermissionDeniedEvent()
@@ -357,7 +357,6 @@ class WeatherForecastViewModel @Inject constructor(
},
{ timeStamp: Long ->
getWeatherForecastService(timeStamp, state.forceNet)
- .await()
.either(
{ failure: Failure ->
Timber.e("loadWeatherForecastFailure: $failure")
diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/di/WeatherForecastModule.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/di/WeatherForecastModule.kt
index 96dda4e9..f673cbb9 100644
--- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/di/WeatherForecastModule.kt
+++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/di/WeatherForecastModule.kt
@@ -2,19 +2,17 @@ package fi.kroon.vadret.presentation.weatherforecast.di
import android.content.Context
import android.location.LocationManager
-import androidx.collection.LruCache
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.squareup.moshi.Moshi
import dagger.Lazy
import dagger.Module
import dagger.Provides
import fi.kroon.vadret.data.location.local.LocationLocalDataSource
import fi.kroon.vadret.data.nominatim.net.NominatimNetDataSource
-import fi.kroon.vadret.data.weatherforecast.model.Weather
import fi.kroon.vadret.data.weatherforecast.net.WeatherForecastNetDataSource
import fi.kroon.vadret.di.qualifiers.Nominatim
import fi.kroon.vadret.di.qualifiers.WeatherQualifier
import fi.kroon.vadret.presentation.weatherforecast.WeatherForecastView
-import fi.kroon.vadret.util.MEMORY_CACHE_SIZE
import fi.kroon.vadret.util.NOMINATIM_BASE_API_URL
import fi.kroon.vadret.util.SMHI_API_FORECAST_URL
import fi.kroon.vadret.util.Scheduler
@@ -22,6 +20,10 @@ import fi.kroon.vadret.util.extension.assertNoInitMainThread
import fi.kroon.vadret.util.extension.delegatingCallFactory
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType
+import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
@@ -31,6 +33,8 @@ import retrofit2.converter.moshi.MoshiConverterFactory
@ExperimentalCoroutinesApi
object WeatherForecastModule {
+ private val contentType: MediaType = "application/json".toMediaType()
+
@Provides
@WeatherForecastScope
fun provideEventChannel(): ConflatedBroadcastChannel =
@@ -44,15 +48,17 @@ object WeatherForecastModule {
@WeatherForecastScope
fun provideSchedulers(): Scheduler = Scheduler()
+ @ExperimentalSerializationApi
@WeatherQualifier
@Provides
@WeatherForecastScope
- fun provideRetrofitWeather(okHttpClient: Lazy, moshi: Moshi): Retrofit {
+ fun provideRetrofitWeather(
+ okHttpClient: Lazy
+ ): Retrofit {
assertNoInitMainThread()
return Retrofit.Builder()
.baseUrl(SMHI_API_FORECAST_URL)
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .addConverterFactory(MoshiConverterFactory.create(moshi))
+ .addConverterFactory(Json.asConverterFactory(contentType))
.delegatingCallFactory(okHttpClient)
.build()
}
@@ -89,10 +95,4 @@ object WeatherForecastModule {
@WeatherForecastScope
fun provideLocationProvider(locationManager: LocationManager): LocationLocalDataSource =
LocationLocalDataSource(locationManager)
-
- @Provides
- @WeatherForecastScope
- fun provideWeatherLruCache(): LruCache = LruCache(
- MEMORY_CACHE_SIZE
- )
}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/util/common/WindChill.kt b/app/src/main/java/fi/kroon/vadret/util/common/WindChill.kt
new file mode 100644
index 00000000..4aad4458
--- /dev/null
+++ b/app/src/main/java/fi/kroon/vadret/util/common/WindChill.kt
@@ -0,0 +1,25 @@
+package fi.kroon.vadret.util.common
+
+import kotlin.math.pow
+
+object WindChill {
+
+ /**
+ * Returns Wind chill as String if applicable or null
+ * T_eff = 13.12 + 0.6215 * T - 13.956 * v^0.16 + 0.48669 * T * v^0.16
+ * v is meter per second
+ * T is temperature in celcius
+ *
+ * Formula: https://www.smhi.se/kunskapsbanken/meteorologi/vindens-kyleffekt-1.259
+ */
+ fun calculate(temperatureCelcius: Double, windSpeedMeterPerSecond: Double): String? {
+
+ if (windSpeedMeterPerSecond < 2 || windSpeedMeterPerSecond > 35) return null
+ if (temperatureCelcius > 10 || temperatureCelcius < -40) return null
+
+ val v: Double = windSpeedMeterPerSecond.pow(0.16)
+ val windChill: Double = 13.12 + 0.6215 * temperatureCelcius - 13.956 * v + (0.48669 * temperatureCelcius * v)
+
+ return "%.1f".format(windChill.toFloat()).replace(",", ".")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fi/kroon/vadret/util/extension/Double.kt b/app/src/main/java/fi/kroon/vadret/util/extension/Double.kt
index f5e5c627..6f39b1f8 100644
--- a/app/src/main/java/fi/kroon/vadret/util/extension/Double.kt
+++ b/app/src/main/java/fi/kroon/vadret/util/extension/Double.kt
@@ -1,7 +1,5 @@
package fi.kroon.vadret.util.extension
-import fi.kroon.vadret.util.MPS_TO_KMPH_FACTOR
-
fun Double.toCoordinate(): Double =
"%.6f".format(this)
.replace(",", ".")
@@ -11,17 +9,4 @@ fun String.toCoordinate(): Double = "%.6f".format(
this.replace("−", "-")
.toDouble()
).replace(",", ".")
- .toDouble()
-fun Double.toWindChill(wind: Double): String {
-
- /**
- * If temperature is <= 10 we do this calculation
- * Reference implementation: https://web.archive.org/web/20060427103553/
- * http://www.msc.ec.gc.ca/education/windchill/science_equations_e.cfm
- */
- val temperature: Double = this
- val kmPh: Double = wind * MPS_TO_KMPH_FACTOR
- val windChill: Double = 13.12 + 0.6215 * temperature - 11.37 * Math.pow(kmPh, 0.16) + (0.3965 * temperature) * Math.pow(kmPh, 0.16)
-
- return "%.1f".format(windChill.toFloat()).replace(",", ".")
-}
\ No newline at end of file
+ .toDouble()
\ No newline at end of file
diff --git a/app/src/main/res/raw/changelog.md b/app/src/main/res/raw/changelog.md
index 9494d5bd..a63037fd 100644
--- a/app/src/main/res/raw/changelog.md
+++ b/app/src/main/res/raw/changelog.md
@@ -5,7 +5,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-## [1.2.10] -- 2020-10-*
+### Fixes
+
+- Fixed a nasty memory leak
+- Fix spelling mistake s/förnärvarande/för närvarande
+
+## [2.0.0] -- 2020-10-31 🎃
+
+This is a major maintenance release that aims to improve the maintainability of this project. A major change
+is the removal of RxJava2 based code and rewriting it with the [Kotlin Coroutines & Flow API](https://github.com/Kotlin/kotlinx.coroutines) instead. This should mean a slight
+performance increase as well. Another big change is that widgets have been removed as per [issue/221](https://github.com/vadret/android/issues/221) since
+they did not work very well and weren't used a lot. The release includes a total of 245 files changed, 2,343 new LOC and 13,134 LOC deleted.
+
+### Fixes
+
+- [issue/229](https://github.com/vadret/android/issues/229) -- Fixes a bug where UTC timestamp was incorrectly parsed to local time
+
+### Added
+
+- Firebase Crashlytics
### Changed
@@ -17,10 +35,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump kotlin 1.4.10
- Replaced travis-ci with Github Actions
- Target Android SDK 30
+- Removed RxJava2 from wetfcst data layer
+- Added kotlinx serialization to wetfcst data layer
### Removed
-- Removed all widgets (see this [issue](https://github.com/vadret/android/issues/221))
+- [issue/221](https://github.com/vadret/android/issues/221) -- Removed all widgets
## [1.2.9] -- 2020-08-17
@@ -214,8 +234,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Radar imagery
- 10 day weather forecast
-[Unreleased]: https://github.com/vadret/android/compare/1.2.10...HEAD
-[1.2.10]: https://github.com/vadret/android/compare/1.2.9...1.2.10
+[Unreleased]: https://github.com/vadret/android/compare/2.0.0...HEAD
+[2.0.0]: https://github.com/vadret/android/compare/1.2.9...2.0.0
[1.2.9]: https://github.com/vadret/android/compare/1.2.8...1.2.9
[1.2.8]: https://github.com/vadret/android/compare/1.2.7...1.2.8
[1.2.7]: https://github.com/vadret/android/compare/1.2.6...1.2.7
diff --git a/app/src/main/res/raw/sweden b/app/src/main/res/raw/sweden
index 751fc89b..0feab58a 100644
--- a/app/src/main/res/raw/sweden
+++ b/app/src/main/res/raw/sweden
@@ -1801,7 +1801,7 @@ Zinkgruvan,Askersund,Örebro,58.815435,15.106626
Åminne,Värnamo,Gotland,57.612497,18.761462
Åmmeberg,Askersund,Örebro,58.865610,14.999192
Åmotfors,Eda,Värmland,59.762586,12.362227
-Åmot,Ockelbo,Värmland,59.705202,12.887207
+Åmot,Ockelbo,Gävleborg,60.965613,16.451243
Åmunnen,Kalmar,Kalmar,56.627696,16.236805
Åmynnet,Örnsköldsvik,Västernorrland,63.186110,18.530696
Åmål,Åmål,Västra Götaland,58.969858,12.618004
diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml
index f4815e07..c8d1d4f9 100644
--- a/app/src/main/res/values-sv-rSE/strings.xml
+++ b/app/src/main/res/values-sv-rSE/strings.xml
@@ -170,5 +170,5 @@
Widget Tema
Risk för åska
Använd min GPS automatiskt
- Inga varning utfärdade förnärvarande.
+ Inga varning utfärdade för närvarande
\ No newline at end of file
diff --git a/app/src/test/java/fi/kroon/vadret/common/WindChillTest.kt b/app/src/test/java/fi/kroon/vadret/common/WindChillTest.kt
index c30059b0..225df9d1 100644
--- a/app/src/test/java/fi/kroon/vadret/common/WindChillTest.kt
+++ b/app/src/test/java/fi/kroon/vadret/common/WindChillTest.kt
@@ -1,26 +1,26 @@
package fi.kroon.vadret.common
-import fi.kroon.vadret.util.extension.toWindChill
+import fi.kroon.vadret.util.common.WindChill
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class WindChillTest {
@Test
- fun `assert toWindChill produces expected results`() {
- val temperature = -20.0
- val windSpeed = 1.388889 // 5km/h in m/s
+ fun `real temperature -10c and windspeed 2 is -34c`() {
+ val temperature = -10.0
+ val windSpeed = 2.0
- val windChill = temperature.toWindChill(windSpeed).toDouble()
- assertThat(windChill).isEqualTo(-24.3)
+ val windChill = WindChill.calculate(temperature, windSpeed)
+ assertThat(windChill).isEqualTo("-14.1")
}
@Test
- fun ` toWindChill produces expected -33 degree result`() {
+ fun `real temperature -20c and windspeed is -34c`() {
val temperature = -20.0
- val windSpeed = 8.333333 // 30km/h in m/s
+ val windSpeed = 10.0
- val windChill = temperature.toWindChill(windSpeed).toDouble()
- assertThat(windChill).isEqualTo(-32.6)
+ val windChill = WindChill.calculate(temperature, windSpeed)
+ assertThat(windChill).isEqualTo("-33.6")
}
}
\ No newline at end of file
diff --git a/app/src/test/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepositoryTest.kt b/app/src/test/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepositoryTest.kt
deleted file mode 100644
index d35a2db5..00000000
--- a/app/src/test/java/fi/kroon/vadret/data/weatherforecast/WeatherForecastRepositoryTest.kt
+++ /dev/null
@@ -1,244 +0,0 @@
-package fi.kroon.vadret.data.weatherforecast
-
-import dagger.Lazy
-import fi.kroon.vadret.data.exception.ErrorHandler
-import fi.kroon.vadret.data.exception.ExceptionHandler
-import fi.kroon.vadret.data.failure.Failure
-import fi.kroon.vadret.data.weatherforecast.exception.WeatherForecastFailure
-import fi.kroon.vadret.data.weatherforecast.model.Weather
-import fi.kroon.vadret.data.weatherforecast.model.WeatherOut
-import fi.kroon.vadret.data.weatherforecast.net.WeatherForecastNetDataSource
-import fi.kroon.vadret.util.DEFAULT_LATITUDE
-import fi.kroon.vadret.util.DEFAULT_LONGITUDE
-import fi.kroon.vadret.util.HTTP_200_OK
-import fi.kroon.vadret.util.HTTP_204_NO_CONTENT
-import fi.kroon.vadret.util.HTTP_400_BAD_REQUEST
-import fi.kroon.vadret.util.HTTP_403_FORBIDDEN
-import fi.kroon.vadret.util.HTTP_404_NOT_FOUND
-import fi.kroon.vadret.util.HTTP_500_INTERNAL_SERVER_ERROR
-import fi.kroon.vadret.util.HTTP_503_SERVICE_UNAVAILABLE
-import fi.kroon.vadret.util.HTTP_504_GATEWAY_TIMEOUT
-import fi.kroon.vadret.util.NetworkHandler
-import fi.kroon.vadret.util.extension.asSingle
-import io.github.sphrak.either.Either
-import io.reactivex.Single
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.doReturn
-import org.mockito.junit.MockitoJUnitRunner
-import retrofit2.Response
-
-@RunWith(MockitoJUnitRunner::class)
-class WeatherForecastRepositoryTest {
-
- private val errorHandler: ErrorHandler = ErrorHandler()
- private val exceptionHandler: ExceptionHandler = ExceptionHandler()
-
- private val weatherForecastRequest: WeatherOut = WeatherOut(
- latitude = DEFAULT_LATITUDE.toDouble(),
- longitude = DEFAULT_LONGITUDE.toDouble()
- )
-
- private lateinit var testWeatherForecastRepository: WeatherForecastRepository
-
- @Mock
- private lateinit var mockWeatherForecastNetDataSource: WeatherForecastNetDataSource
-
- private var mockWeatherForecastNetDataSourceLazy = Lazy {
- mockWeatherForecastNetDataSource
- }
-
- @Mock
- private lateinit var mockNetworkHandler: NetworkHandler
-
- @Mock
- private lateinit var mockWeatherResponse: Response
-
- @Mock
- private lateinit var mockWeatherForecast: Weather
-
- @Before
- fun setup() {
- testWeatherForecastRepository = WeatherForecastRepository(
- mockWeatherForecastNetDataSourceLazy,
- mockNetworkHandler,
- errorHandler = errorHandler,
- exceptionHandler = exceptionHandler
- )
- }
-
- @Test
- fun `repository returns WeatherIn object correctly`() {
-
- doReturn(HTTP_200_OK).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(mockWeatherForecast).`when`(mockWeatherResponse).body()
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
-
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Right && it.b == mockWeatherForecast }
- }
-
- @Test
- fun `repository returns not available when NOT_CONNECTED`() {
-
- doReturn(false).`when`(mockNetworkHandler).isConnected
-
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.NetworkOfflineError }
- }
-
- @Test
- fun `repository returns HTTP_204_NO_CONTENT failure`() {
-
- doReturn(HTTP_204_NO_CONTENT).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
-
- doReturn(mockWeatherResponse.asSingle())
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is WeatherForecastFailure.NoWeatherAvailable }
- }
-
- @Test
- fun `repository returns HTTP_403_FORBIDDEN failure`() {
-
- doReturn(HTTP_403_FORBIDDEN).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get()).getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.HttpForbidden403 }
- }
-
- @Test
- fun `repository returns HTTP_404_NOT_FOUND failure`() {
-
- doReturn(HTTP_404_NOT_FOUND).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is WeatherForecastFailure.NoWeatherAvailableForThisLocation }
- }
-
- @Test
- fun `repository returns HTTP_400_BAD_REQUEST failure`() {
-
- doReturn(HTTP_400_BAD_REQUEST).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get()).getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.HttpBadRequest400 }
- }
-
- @Test
- fun `repository returns HTTP_500_INTERNAL_SERVER_ERROR failure`() {
-
- doReturn(HTTP_500_INTERNAL_SERVER_ERROR).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.HttpInternalServerError500 }
- }
-
- @Test
- fun `repository returns HTTP_503_SERVICE_UNAVAILABLE failure`() {
-
- doReturn(HTTP_503_SERVICE_UNAVAILABLE).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.HttpServiceUnavailable503 }
- }
-
- @Test
- fun `repository returns HTTP_504_GATEWAY_TIMEOUT failure`() {
-
- doReturn(HTTP_504_GATEWAY_TIMEOUT).`when`(mockWeatherResponse).code()
- doReturn(true).`when`(mockNetworkHandler).isConnected
- doReturn(Single.just(mockWeatherResponse))
- .`when`(mockWeatherForecastNetDataSourceLazy.get())
- .getWeatherForecast(
- weatherForecastRequest.category,
- weatherForecastRequest.version,
- weatherForecastRequest.longitude,
- weatherForecastRequest.latitude
- )
- testWeatherForecastRepository(weatherForecastRequest)
- .test()
- .assertComplete()
- .assertNoErrors()
- .assertValueAt(0) { it is Either.Left && it.a is Failure.HttpGatewayTimeout504 }
- }
-}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 0db4d15d..d02dc88e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,10 +5,15 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.2'
+ classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
+ classpath "org.jetbrains.kotlin:kotlin-serialization:$KOTLIN_VERSION"
+ classpath 'com.google.gms:google-services:4.3.4'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
}
+
}
+
allprojects {
repositories {
google()
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0902649e..3e5e5e80 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
\ No newline at end of file
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
\ No newline at end of file