diff --git a/build.gradle b/build.gradle index 61b7079..cb8e5d4 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ buildscript { repositories { google() jcenter() + mavenCentral() } dependencies { // Kotlin @@ -27,6 +28,7 @@ allprojects { repositories { google() jcenter() + mavenCentral() } group = 'org.koin' diff --git a/examples/android-weather-app/build.gradle b/examples/android-weather-app/build.gradle index 11d76bd..ded861b 100644 --- a/examples/android-weather-app/build.gradle +++ b/examples/android-weather-app/build.gradle @@ -1,7 +1,8 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' -apply plugin: 'kotlin-android-extensions' +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' +} apply from: '../../gradle/versions-examples.gradle' @@ -16,7 +17,7 @@ android { versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // used by Room, to test migrations javaCompileOptions { @@ -27,7 +28,7 @@ android { } } testOptions { - execution 'ANDROID_TEST_ORCHESTRATOR' + execution 'ANDROIDX_TEST_ORCHESTRATOR' } buildTypes { release { @@ -43,57 +44,57 @@ android { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } + buildFeatures { + viewBinding true + } } dependencies { - // Android Support - implementation "com.android.support:appcompat-v7:$support_lib_version" - implementation "com.android.support:support-v4:$support_lib_version" - implementation "com.android.support:design:$support_lib_version" + // AndroidX + implementation "androidx.appcompat:appcompat:1.3.1" + implementation "androidx.appcompat:appcompat-resources:1.3.1" // Android Test testImplementation "junit:junit:$junit_version" testImplementation "org.mockito:mockito-inline:$mockito_version" - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestUtil 'com.android.support.test:orchestrator:1.0.2' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestUtil 'androidx.test:orchestrator:1.4.0' - // Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - // Anko - implementation "org.jetbrains.anko:anko-commons:$anko_version" // Koin - implementation "org.koin:koin-android-viewmodel:$koin_version" - testImplementation "org.koin:koin-test:$koin_version" - androidTestImplementation "org.koin:koin-test:$koin_version" + implementation "io.insert-koin:koin-android:$koin_version" + testImplementation "io.insert-koin:koin-test:$koin_version" + androidTestImplementation "io.insert-koin:koin-test:$koin_version" // ViewModel and LiveData - implementation "android.arch.lifecycle:extensions:$android_arch_version" - testImplementation "android.arch.core:core-testing:$android_arch_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + testImplementation "androidx.arch.core:core-testing:$arch_version" // Room - implementation "android.arch.persistence.room:runtime:$room_version" - implementation "android.arch.persistence.room:rxjava2:$room_version" - kapt "android.arch.persistence.room:compiler:$room_version" - annotationProcessor "android.arch.persistence.room:compiler:$room_version" - testImplementation "android.arch.persistence.room:testing:$room_version" + implementation "androidx.room:room-runtime:$room_version" + implementation "androidx.room:room-rxjava3:$room_version" + kapt "androidx.room:room-compiler:$room_version" + annotationProcessor "androidx.room:room-compiler:$room_version" + testImplementation "androidx.room:room-testing:$room_version" // UI - implementation "com.android.support.constraint:constraint-layout:$constraint_layout_version" + implementation 'com.google.android.material:material:1.4.0' + implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version" implementation 'com.joanzapata.iconify:android-iconify-weathericons:2.2.2' // Gson - implementation 'com.google.code.gson:gson:2.8.2' + implementation 'com.google.code.gson:gson:2.8.8' // Networking implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" - implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version" + implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" // Rx - implementation "io.reactivex.rxjava2:rxjava:$rxjava_version" - implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation "io.reactivex.rxjava3:rxjava:$rxjava_version" + implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' // Canary debugImplementation "com.squareup.leakcanary:leakcanary-android:$leak_canary_version" diff --git a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherDAOTest.kt b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherDAOTest.kt index 05fc250..5885da8 100644 --- a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherDAOTest.kt +++ b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherDAOTest.kt @@ -1,14 +1,14 @@ package fr.ekito.myweatherapp -import android.support.test.runner.AndroidJUnit4 +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import fr.ekito.myweatherapp.data.WeatherDataSource import fr.ekito.myweatherapp.data.room.WeatherDAO import fr.ekito.myweatherapp.data.room.WeatherDatabase import fr.ekito.myweatherapp.data.room.WeatherEntity import fr.ekito.myweatherapp.domain.ext.getDailyForecasts import fr.ekito.myweatherapp.domain.ext.getLocation -import junit.framework.Assert import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -16,16 +16,16 @@ import org.koin.core.context.loadKoinModules import org.koin.core.context.stopKoin import org.koin.test.KoinTest import org.koin.test.inject -import java.util.Date +import java.util.* -@RunWith(AndroidJUnit4::class) +@RunWith(AndroidJUnit4ClassRunner::class) class WeatherDAOTest : KoinTest { - val weatherDatabase: WeatherDatabase by inject() - val weatherWebDatasource: WeatherDataSource by inject() - val weatherDAO: WeatherDAO by inject() + private val weatherDatabase: WeatherDatabase by inject() + private val weatherWebDatasource: WeatherDataSource by inject() + private val weatherDAO: WeatherDAO by inject() - @Before() + @Before fun before() { loadKoinModules(roomTestModule) } @@ -48,7 +48,7 @@ class WeatherDAOTest : KoinTest { val requestedEntities = ids.map { weatherDAO.findWeatherById(it).blockingGet() } - Assert.assertEquals(entities, requestedEntities) + assertEquals(entities, requestedEntities) } @Test @@ -65,7 +65,7 @@ class WeatherDAOTest : KoinTest { val resultList = weatherDAO.findAllBy(locationTlse, dateToulouse).blockingGet() - Assert.assertEquals(weatherToulouse, resultList) + assertEquals(weatherToulouse, resultList) } @Test @@ -88,7 +88,7 @@ class WeatherDAOTest : KoinTest { val result: WeatherEntity = weatherDAO.findLatestWeather().blockingGet().first() val resultList = weatherDAO.findAllBy(result.location, result.date).blockingGet() - Assert.assertEquals(weatherToulouse, resultList) + assertEquals(weatherToulouse, resultList) } private fun getWeatherAsEntities( @@ -97,7 +97,7 @@ class WeatherDAOTest : KoinTest { ): List { return weatherWebDatasource.geocode(locationParis) .map { it.getLocation() } - .flatMap { weatherWebDatasource.weather(it.lat, it.lng, "EN") } + .flatMap { weatherWebDatasource.weather(it?.lat, it?.lng, "EN") } .map { it.getDailyForecasts(locationParis) } .map { list -> list.map { WeatherEntity.from(it, dateParis) } } .blockingGet() diff --git a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherRepositoryTest.kt b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherRepositoryTest.kt index c4b4fce..c2af609 100644 --- a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherRepositoryTest.kt +++ b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/WeatherRepositoryTest.kt @@ -1,9 +1,9 @@ package fr.ekito.myweatherapp -import android.support.test.runner.AndroidJUnit4 +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository -import junit.framework.Assert import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -12,12 +12,12 @@ import org.koin.core.context.stopKoin import org.koin.test.KoinTest import org.koin.test.inject -@RunWith(AndroidJUnit4::class) +@RunWith(AndroidJUnit4ClassRunner::class) class WeatherRepositoryTest : KoinTest { private val weatherRepository: DailyForecastRepository by inject() - @Before() + @Before fun before() { loadKoinModules(roomTestModule) } @@ -31,7 +31,7 @@ class WeatherRepositoryTest : KoinTest { fun testGetDefault() { val defaultWeather = weatherRepository.getWeather().blockingGet() val defaultWeather2 = weatherRepository.getWeather().blockingGet() - Assert.assertEquals(defaultWeather, defaultWeather2) + assertEquals(defaultWeather, defaultWeather2) } @Test @@ -40,7 +40,7 @@ class WeatherRepositoryTest : KoinTest { val result = defaultWeather.first() val first = weatherRepository.getWeatherDetail(result.id).blockingGet() - Assert.assertEquals(result, first) + assertEquals(result, first) } @Test @@ -49,6 +49,6 @@ class WeatherRepositoryTest : KoinTest { weatherRepository.getWeather("London").blockingGet() val toulouse = weatherRepository.getWeather("Toulouse").blockingGet() val defaultWeather3 = weatherRepository.getWeather().blockingGet() - Assert.assertEquals(defaultWeather3, toulouse) + assertEquals(defaultWeather3, toulouse) } } \ No newline at end of file diff --git a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/room_test_modules.kt b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/room_test_modules.kt index b4f8bb1..d82609d 100644 --- a/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/room_test_modules.kt +++ b/examples/android-weather-app/src/androidTest/java/fr/ekito/myweatherapp/room_test_modules.kt @@ -1,11 +1,11 @@ package fr.ekito.myweatherapp -import android.arch.persistence.room.Room +import androidx.room.Room import fr.ekito.myweatherapp.data.room.WeatherDatabase import org.koin.dsl.module -// Room In memroy database -val roomTestModule = module(override = true) { +// Room In memory database +val roomTestModule = module { single { Room.inMemoryDatabaseBuilder(get(), WeatherDatabase::class.java) .allowMainThreadQueries() diff --git a/examples/android-weather-app/src/main/AndroidManifest.xml b/examples/android-weather-app/src/main/AndroidManifest.xml index 238efed..0f300d8 100644 --- a/examples/android-weather-app/src/main/AndroidManifest.xml +++ b/examples/android-weather-app/src/main/AndroidManifest.xml @@ -13,7 +13,8 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/MainApplication.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/MainApplication.kt index 8e5049b..0198d26 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/MainApplication.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/MainApplication.kt @@ -5,6 +5,7 @@ import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.fonts.WeathericonsModule import fr.ekito.myweatherapp.di.roomWeatherApp import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidFileProperties import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin import org.koin.core.logger.Level @@ -19,13 +20,12 @@ class MainApplication : Application() { // start Koin context startKoin { - fileProperties() androidLogger(Level.DEBUG) androidContext(this@MainApplication) + androidFileProperties() modules(roomWeatherApp) } - Iconify - .with(WeathericonsModule()) + Iconify.with(WeathericonsModule()) } } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/WeatherDataSource.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/WeatherDataSource.kt index 797bfe3..ce86a16 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/WeatherDataSource.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/WeatherDataSource.kt @@ -2,7 +2,7 @@ package fr.ekito.myweatherapp.data import fr.ekito.myweatherapp.data.json.Geocode import fr.ekito.myweatherapp.data.json.Weather -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.Query diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Geocode.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Geocode.kt index e803035..f64ec04 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Geocode.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Geocode.kt @@ -1,7 +1,5 @@ package fr.ekito.myweatherapp.data.json -import java.util.* - data class AddressComponent( val long_name: String? = null, val short_name: String? = null, diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Weather.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Weather.kt index 012f186..99eb962 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Weather.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/json/Weather.kt @@ -1,7 +1,5 @@ package fr.ekito.myweatherapp.data.json -import java.util.* - data class Avewind( val mph: Int? = null, val kph: Int? = null, diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/AndroidJsonReader.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/AndroidJsonReader.kt index 936c223..a44c430 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/AndroidJsonReader.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/AndroidJsonReader.kt @@ -9,11 +9,11 @@ import java.io.InputStreamReader */ class AndroidJsonReader(val application: Application) : BaseReader() { - override fun getAllFiles(): List = application.assets.list("json").toList() + override fun getAllFiles(): List = application.assets.list("json")!!.toList() override fun readJsonFile(jsonFile: String): String { val buf = StringBuilder() - val json = application.assets.open("json/" + jsonFile) + val json = application.assets.open("json/$jsonFile") BufferedReader(InputStreamReader(json, "UTF-8")) .use { val list = it.lineSequence().toList() diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/FileDataSource.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/FileDataSource.kt index 6873778..c05f249 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/FileDataSource.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/local/FileDataSource.kt @@ -3,13 +3,14 @@ package fr.ekito.myweatherapp.data.local import fr.ekito.myweatherapp.data.WeatherDataSource import fr.ekito.myweatherapp.data.json.Geocode import fr.ekito.myweatherapp.data.json.Weather -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single +import java.util.Locale import java.util.concurrent.TimeUnit /** * Read json files and render weather date */ -class FileDataSource(val jsonReader: JsonReader, val delayed: Boolean) : +class FileDataSource(private val jsonReader: JsonReader, private val delayed: Boolean) : WeatherDataSource { private val cities by lazy { jsonReader.getAllLocations() } @@ -23,7 +24,7 @@ class FileDataSource(val jsonReader: JsonReader, val delayed: Boolean) : override fun geocode(address: String): Single { val single = Single.create { s -> - val addressToLC = address.toLowerCase() + val addressToLC = address.lowercase(Locale.getDefault()) val geocode = if (isKnownCity(addressToLC)) { jsonReader.getGeocode(addressToLC) } else { diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/Converters.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/Converters.kt index dcaa607..4d9bcc0 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/Converters.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/Converters.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.data.room -import android.arch.persistence.room.TypeConverter -import java.util.* +import androidx.room.TypeConverter +import java.util.Date class Converters { @TypeConverter diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDAO.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDAO.kt index 13b2f07..d402288 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDAO.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDAO.kt @@ -1,10 +1,10 @@ package fr.ekito.myweatherapp.data.room -import android.arch.persistence.room.Dao -import android.arch.persistence.room.Insert -import android.arch.persistence.room.Query -import io.reactivex.Single -import java.util.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import io.reactivex.rxjava3.core.Single +import java.util.Date @Dao interface WeatherDAO { diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDatabase.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDatabase.kt index 75d7514..429c299 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDatabase.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherDatabase.kt @@ -1,8 +1,8 @@ package fr.ekito.myweatherapp.data.room -import android.arch.persistence.room.Database -import android.arch.persistence.room.RoomDatabase -import android.arch.persistence.room.TypeConverters +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters @Database(entities = [WeatherEntity::class], version = 1) @TypeConverters(Converters::class) diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherEntity.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherEntity.kt index 7c15c89..311d4c1 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherEntity.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/data/room/WeatherEntity.kt @@ -1,9 +1,9 @@ package fr.ekito.myweatherapp.data.room -import android.arch.persistence.room.Entity -import android.arch.persistence.room.PrimaryKey +import androidx.room.Entity +import androidx.room.PrimaryKey import fr.ekito.myweatherapp.domain.entity.DailyForecast -import java.util.* +import java.util.Date @Entity(tableName = "weather") data class WeatherEntity( diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/app_module.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/app_module.kt index d6d2250..1f38f3f 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/app_module.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/app_module.kt @@ -7,7 +7,7 @@ import fr.ekito.myweatherapp.util.coroutines.SchedulerProvider import fr.ekito.myweatherapp.view.detail.DetailViewModel import fr.ekito.myweatherapp.view.splash.SplashViewModel import fr.ekito.myweatherapp.view.weather.WeatherViewModel -import org.koin.android.viewmodel.dsl.viewModel +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module /** diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/remote_datasource_module.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/remote_datasource_module.kt index 8e03535..c4c7d60 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/remote_datasource_module.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/remote_datasource_module.kt @@ -6,7 +6,7 @@ import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.koin.dsl.module import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit @@ -38,6 +38,6 @@ inline fun createWebService(okHttpClient: OkHttpClient, url: String) .baseUrl(url) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build() + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()).build() return retrofit.create(T::class.java) } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/room_datasource_module.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/room_datasource_module.kt index b5d9cc7..aa93c7f 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/room_datasource_module.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/di/room_datasource_module.kt @@ -1,6 +1,7 @@ package fr.ekito.myweatherapp.di -import android.arch.persistence.room.Room + +import androidx.room.Room import fr.ekito.myweatherapp.data.room.WeatherDatabase import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.domain.repository.DailyForecastRepositoryRoomImpl @@ -10,7 +11,7 @@ import org.koin.dsl.module val roomDataSourceModule = module { // Weather Room Data Repository - single(override = true) { + single { DailyForecastRepositoryRoomImpl( get(), get() diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepository.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepository.kt index 0bc4eb2..1654a0e 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepository.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepository.kt @@ -4,7 +4,7 @@ import fr.ekito.myweatherapp.data.WeatherDataSource import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.ext.getDailyForecasts import fr.ekito.myweatherapp.domain.ext.getLocation -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single /** * Weather repository diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepositoryRoomImpl.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepositoryRoomImpl.kt index d800146..6f375ec 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepositoryRoomImpl.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/domain/repository/DailyForecastRepositoryRoomImpl.kt @@ -6,8 +6,8 @@ import fr.ekito.myweatherapp.data.room.WeatherEntity import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.ext.getDailyForecasts import fr.ekito.myweatherapp.domain.ext.getLocation -import io.reactivex.Single -import java.util.* +import io.reactivex.rxjava3.core.Single +import java.util.Date class DailyForecastRepositoryRoomImpl( private val weatherDatasource: WeatherDataSource, diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/android/FragmentActivityExt.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/android/FragmentActivityExt.kt index f7a8740..07b9c2c 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/android/FragmentActivityExt.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/android/FragmentActivityExt.kt @@ -2,11 +2,11 @@ package fr.ekito.myweatherapp.util.android -import android.support.v4.app.FragmentActivity +import androidx.fragment.app.FragmentActivity /** * Retrieve argument from Activity intent */ fun FragmentActivity.argument(key: String) = - lazy { intent.extras[key] as? T ?: error("Intent Argument $key is missing") } + lazy { intent.extras?.get(key) as? T ?: error("Intent Argument $key is missing") } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/ApplicationSchedulerProvider.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/ApplicationSchedulerProvider.kt index cd33db2..4ad9a36 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/ApplicationSchedulerProvider.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/ApplicationSchedulerProvider.kt @@ -1,15 +1,16 @@ package fr.ekito.myweatherapp.util.coroutines -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.schedulers.Schedulers /** * Application providers */ class ApplicationSchedulerProvider : SchedulerProvider { - override fun io() = Schedulers.io() + override fun io(): Scheduler = Schedulers.io() - override fun ui() = AndroidSchedulers.mainThread() + override fun ui(): Scheduler = AndroidSchedulers.mainThread() - override fun computation() = Schedulers.computation() + override fun computation(): Scheduler = Schedulers.computation() } \ No newline at end of file diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/RxWithExt.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/RxWithExt.kt index 7e323d2..f102f05 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/RxWithExt.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/RxWithExt.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.util.coroutines -import io.reactivex.Completable -import io.reactivex.Single +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single /** diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/SchedulerProvider.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/SchedulerProvider.kt index 13948af..bc3922b 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/SchedulerProvider.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/coroutines/SchedulerProvider.kt @@ -1,6 +1,6 @@ package fr.ekito.myweatherapp.util.coroutines -import io.reactivex.Scheduler +import io.reactivex.rxjava3.core.Scheduler /** * Rx Scheduler Provider diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/RxViewModel.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/RxViewModel.kt index bc0b2b3..ec4eacb 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/RxViewModel.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/RxViewModel.kt @@ -1,9 +1,9 @@ package fr.ekito.myweatherapp.util.mvvm -import android.arch.lifecycle.ViewModel -import android.support.annotation.CallSuper -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable +import androidx.annotation.CallSuper +import androidx.lifecycle.ViewModel +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.disposables.Disposable /** * ViewModel for Rx Jobs diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/SingleLiveEvent.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/SingleLiveEvent.kt index b26a4c2..783b10c 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/SingleLiveEvent.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/util/mvvm/SingleLiveEvent.kt @@ -1,10 +1,10 @@ package fr.ekito.myweatherapp.util.mvvm -import android.arch.lifecycle.LifecycleOwner -import android.arch.lifecycle.MutableLiveData -import android.arch.lifecycle.Observer -import android.support.annotation.MainThread import android.util.Log +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer import java.util.concurrent.atomic.AtomicBoolean /** @@ -28,15 +28,13 @@ class SingleLiveEvent : MutableLiveData() { private val pending = AtomicBoolean(false) - @MainThread - override fun observe(owner: LifecycleOwner, observer: Observer) { - + override fun observe(owner: LifecycleOwner, observer: Observer) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") } // Observe the internal MutableLiveData - super.observe(owner, Observer { t -> + super.observe(owner, { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailActivity.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailActivity.kt index 7ea807b..2c2474f 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailActivity.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailActivity.kt @@ -1,16 +1,15 @@ package fr.ekito.myweatherapp.view.detail -import android.arch.lifecycle.Observer import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.snackbar.Snackbar import fr.ekito.myweatherapp.R +import fr.ekito.myweatherapp.databinding.ActivityDetailBinding import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.entity.getColorFromCode import fr.ekito.myweatherapp.util.android.argument import fr.ekito.myweatherapp.view.Failed -import kotlinx.android.synthetic.main.activity_detail.* -import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf /** @@ -18,16 +17,19 @@ import org.koin.core.parameter.parametersOf */ class DetailActivity : AppCompatActivity() { + private lateinit var binding: ActivityDetailBinding + // Get all needed data private val detailId by argument(INTENT_WEATHER_ID) - val detailViewModel: DetailViewModel by viewModel { parametersOf(detailId) } + private val detailViewModel: DetailViewModel by viewModel { parametersOf(detailId) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_detail) + binding = ActivityDetailBinding.inflate(layoutInflater) + setContentView(binding.root) - detailViewModel.states.observe(this, Observer { state -> + detailViewModel.states.observe(this, { state -> when (state) { is Failed -> showError(state.error) is DetailViewModel.DetailLoaded -> showDetail(state.weather) @@ -36,25 +38,27 @@ class DetailActivity : AppCompatActivity() { detailViewModel.getDetail() } - fun showError(error: Throwable) { + private fun showError(error: Throwable) { Snackbar.make( - weatherItem, + binding.weatherItem, getString(R.string.loading_error) + " - $error", Snackbar.LENGTH_LONG ).show() } - fun showDetail(weather: DailyForecast) { - weatherIcon.text = weather.icon - weatherDay.text = weather.day - weatherText.text = weather.fullText - weatherWindText.text = weather.wind.toString() - weatherTempText.text = weather.temperature.toString() - weatherHumidityText.text = weather.humidity.toString() - weatherItem.background.setTint(getColorFromCode(weather)) - // Set back on background click - weatherItem.setOnClickListener { - onBackPressed() + private fun showDetail(weather: DailyForecast) { + with(binding) { + weatherIcon.text = weather.icon + weatherDay.text = weather.day + weatherText.text = weather.fullText + weatherWindText.text = weather.wind.toString() + weatherTempText.text = weather.temperature.toString() + weatherHumidityText.text = weather.humidity.toString() + weatherItem.background.setTint(getColorFromCode(weather)) + // Set back on background click + weatherItem.setOnClickListener { + onBackPressed() + } } } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailViewModel.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailViewModel.kt index b260382..9c9ef1a 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailViewModel.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/detail/DetailViewModel.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.view.detail -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.util.mvvm.RxViewModel diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashActivity.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashActivity.kt index 569dc5e..58fc281 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashActivity.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashActivity.kt @@ -1,35 +1,34 @@ package fr.ekito.myweatherapp.view.splash -import android.arch.lifecycle.Observer +import android.content.Intent import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity import android.view.View import android.view.animation.AnimationUtils +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.snackbar.Snackbar import fr.ekito.myweatherapp.R +import fr.ekito.myweatherapp.databinding.ActivitySplashBinding import fr.ekito.myweatherapp.view.Error import fr.ekito.myweatherapp.view.Pending import fr.ekito.myweatherapp.view.Success import fr.ekito.myweatherapp.view.weather.WeatherActivity -import kotlinx.android.synthetic.main.activity_splash.* -import org.jetbrains.anko.clearTask -import org.jetbrains.anko.clearTop -import org.jetbrains.anko.intentFor -import org.jetbrains.anko.newTask -import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.viewModel /** * Search Weather View */ class SplashActivity : AppCompatActivity() { + private lateinit var binding: ActivitySplashBinding + private val splashViewModel: SplashViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_splash) + binding = ActivitySplashBinding.inflate(layoutInflater) + setContentView(binding.root) - splashViewModel.events.observe(this, Observer { event -> + splashViewModel.events.observe(this, { event -> when (event) { is Pending -> showIsLoading() is Success -> showIsLoaded() @@ -41,21 +40,29 @@ class SplashActivity : AppCompatActivity() { private fun showIsLoading() { val animation = - AnimationUtils.loadAnimation(applicationContext, R.anim.infinite_blinking_animation) - splashIcon.startAnimation(animation) + AnimationUtils.loadAnimation(applicationContext, R.anim.infinite_blinking_animation) + binding.splashIcon.startAnimation(animation) } private fun showIsLoaded() { - startActivity(intentFor().clearTop().clearTask().newTask()) + startActivity( + Intent(this, WeatherActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) } private fun showError(error: Throwable) { - splashIcon.visibility = View.GONE - splashIconFail.visibility = View.VISIBLE - Snackbar.make(splash, "SplashActivity got error : $error", Snackbar.LENGTH_INDEFINITE) + with(binding) { + splashIcon.visibility = View.GONE + splashIconFail.visibility = View.VISIBLE + Snackbar.make(splash, "SplashActivity got error : $error", Snackbar.LENGTH_INDEFINITE) .setAction(R.string.retry) { splashViewModel.getLastWeather() } .show() + } } } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashViewModel.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashViewModel.kt index c51ac31..e2555f7 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashViewModel.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/splash/SplashViewModel.kt @@ -1,6 +1,6 @@ package fr.ekito.myweatherapp.view.splash -import android.arch.lifecycle.LiveData +import androidx.lifecycle.LiveData import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.util.mvvm.RxViewModel import fr.ekito.myweatherapp.util.mvvm.SingleLiveEvent @@ -24,7 +24,7 @@ class SplashViewModel( _events.value = Pending launch { dailyForecastRepository.getWeather().with(schedulerProvider) - .toCompletable() + .ignoreElement() .subscribe( { _events.value = Success }, { error -> _events.value = Error(error) }) diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherActivity.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherActivity.kt index f04a492..819e13b 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherActivity.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherActivity.kt @@ -1,46 +1,47 @@ package fr.ekito.myweatherapp.view.weather -import android.arch.lifecycle.Observer +import android.content.Intent import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity import android.util.Log import android.view.View +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.snackbar.Snackbar import fr.ekito.myweatherapp.R +import fr.ekito.myweatherapp.databinding.ActivityResultBinding import fr.ekito.myweatherapp.view.Failed -import kotlinx.android.synthetic.main.activity_result.* -import org.jetbrains.anko.clearTask -import org.jetbrains.anko.clearTop -import org.jetbrains.anko.intentFor -import org.jetbrains.anko.newTask -import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.androidx.viewmodel.ext.android.viewModel /** * Weather Result View */ class WeatherActivity : AppCompatActivity() { - private val TAG = this::class.java.simpleName + companion object { + private val TAG = this::class.java.simpleName + } + + private lateinit var binding: ActivityResultBinding private val viewModel: WeatherViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_result) + binding = ActivityResultBinding.inflate(layoutInflater) + setContentView(binding.root) val weatherTitleFragment = WeatherHeaderFragment() val resultListFragment = WeatherListFragment() supportFragmentManager - .beginTransaction() - .replace(R.id.weather_title, weatherTitleFragment) - .commit() + .beginTransaction() + .replace(R.id.weather_title, weatherTitleFragment) + .commit() supportFragmentManager - .beginTransaction() - .replace(R.id.weather_list, resultListFragment) - .commit() + .beginTransaction() + .replace(R.id.weather_list, resultListFragment) + .commit() - viewModel.states.observe(this, Observer { state -> + viewModel.states.observe(this, { state -> when (state) { is Failed -> showError(state.error) } @@ -51,16 +52,24 @@ class WeatherActivity : AppCompatActivity() { private fun showError(error: Throwable) { Log.e(TAG, "error $error while displaying weather") - weather_views.visibility = View.GONE - weather_error.visibility = View.VISIBLE - Snackbar.make( - weather_result, + with(binding) { + weatherViews.visibility = View.GONE + weatherError.visibility = View.VISIBLE + Snackbar.make( + weatherResult, "WeatherActivity got error : $error", Snackbar.LENGTH_INDEFINITE - ) + ) .setAction(R.string.retry) { - startActivity(intentFor().clearTop().clearTask().newTask()) + startActivity( + Intent(this@WeatherActivity, WeatherActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ) } .show() + } } } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherHeaderFragment.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherHeaderFragment.kt index 49a9b2d..19fa288 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherHeaderFragment.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherHeaderFragment.kt @@ -1,72 +1,85 @@ package fr.ekito.myweatherapp.view.weather import android.app.AlertDialog -import android.arch.lifecycle.Observer +import android.content.Intent import android.os.Bundle -import android.support.design.widget.Snackbar -import android.support.v4.app.Fragment import android.text.InputType import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar import fr.ekito.myweatherapp.R +import fr.ekito.myweatherapp.databinding.FragmentResultHeaderBinding import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.entity.getColorFromCode import fr.ekito.myweatherapp.view.detail.DetailActivity import fr.ekito.myweatherapp.view.detail.DetailActivity.Companion.INTENT_WEATHER_ID -import kotlinx.android.synthetic.main.fragment_result_header.* -import org.jetbrains.anko.startActivity -import org.koin.android.viewmodel.ext.android.sharedViewModel +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class WeatherHeaderFragment : Fragment() { + private var _binding: FragmentResultHeaderBinding? = null + private val binding get() = _binding!! + private val viewModel: WeatherViewModel by sharedViewModel() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? ): View { - return inflater.inflate(R.layout.fragment_result_header, container, false) as ViewGroup + _binding = FragmentResultHeaderBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.states.observe(this, Observer { state -> + viewModel.states.observe(viewLifecycleOwner, { state -> when (state) { is WeatherViewModel.WeatherListLoaded -> showWeather(state.location, state.first) } }) - viewModel.events.observe(this, Observer { event -> + viewModel.events.observe(viewLifecycleOwner, { event -> when (event) { is WeatherViewModel.ProceedLocation -> showLoadingLocation(event.location) is WeatherViewModel.ProceedLocationError -> showLocationSearchFailed( - event.location, - event.error + event.location, + event.error ) } }) } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + private fun showWeather(location: String, weather: DailyForecast) { - weatherCity.text = location - weatherCityCard.setOnClickListener { - promptLocationDialog() - } + with(binding) { + weatherCity.text = location + weatherCityCard.setOnClickListener { + promptLocationDialog() + } - weatherIcon.text = weather.icon - weatherDay.text = weather.day - weatherTempText.text = weather.temperature.toString() - weatherText.text = weather.shortText + weatherIcon.text = weather.icon + weatherDay.text = weather.day + weatherTempText.text = weather.temperature.toString() + weatherText.text = weather.shortText - val color = context!!.getColorFromCode(weather) - weatherHeader.background.setTint(color) + val color = requireContext().getColorFromCode(weather) + weatherHeader.background.setTint(color) - weatherHeader.setOnClickListener { - activity?.startActivity( - INTENT_WEATHER_ID to weather.id - ) + weatherHeader.setOnClickListener { + activity?.startActivity( + Intent(context, DetailActivity::class.java).putExtra( + INTENT_WEATHER_ID, + weather.id + ) + ) + } } } @@ -91,18 +104,18 @@ class WeatherHeaderFragment : Fragment() { private fun showLoadingLocation(location: String) { Snackbar.make( - weatherHeader, - getString(R.string.loading_location) + " $location ...", - Snackbar.LENGTH_LONG + binding.weatherHeader, + getString(R.string.loading_location) + " $location ...", + Snackbar.LENGTH_LONG ) - .show() + .show() } private fun showLocationSearchFailed(location: String, error: Throwable) { - Snackbar.make(weatherHeader, getString(R.string.loading_error), Snackbar.LENGTH_LONG) - .setAction(R.string.retry) { - viewModel.loadNewLocation(location) - } - .show() + Snackbar.make(binding.weatherHeader, getString(R.string.loading_error), Snackbar.LENGTH_LONG) + .setAction(R.string.retry) { + viewModel.loadNewLocation(location) + } + .show() } } \ No newline at end of file diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherListFragment.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherListFragment.kt index 233a52f..419c958 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherListFragment.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherListFragment.kt @@ -1,36 +1,38 @@ package fr.ekito.myweatherapp.view.weather -import android.arch.lifecycle.Observer +import android.content.Intent import android.os.Bundle -import android.support.v4.app.Fragment -import android.support.v7.widget.LinearLayoutManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import fr.ekito.myweatherapp.R +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import fr.ekito.myweatherapp.databinding.FragmentResultListBinding import fr.ekito.myweatherapp.view.detail.DetailActivity import fr.ekito.myweatherapp.view.weather.list.WeatherItem import fr.ekito.myweatherapp.view.weather.list.WeatherListAdapter -import kotlinx.android.synthetic.main.fragment_result_list.* -import org.jetbrains.anko.startActivity -import org.koin.android.viewmodel.ext.android.sharedViewModel +import org.koin.androidx.viewmodel.ext.android.sharedViewModel class WeatherListFragment : Fragment() { + private var _binding: FragmentResultListBinding? = null + private val binding get() = _binding!! + private val viewModel: WeatherViewModel by sharedViewModel() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_result_list, container, false) + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentResultListBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) prepareListView() - viewModel.states.observe(this, Observer { state -> + viewModel.states.observe(viewLifecycleOwner, { state -> when (state) { is WeatherViewModel.WeatherListLoaded -> showWeatherItemList(state.lasts.map { WeatherItem.from(it) @@ -39,24 +41,34 @@ class WeatherListFragment : Fragment() { }) } + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + private fun prepareListView() { - weatherList.layoutManager = + with(binding) { + weatherList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - weatherList.adapter = WeatherListAdapter( - activity!!, + weatherList.adapter = WeatherListAdapter( + requireActivity(), emptyList(), ::onWeatherItemSelected - ) + ) + } } private fun onWeatherItemSelected(resultItem: WeatherItem) { - activity?.startActivity( - DetailActivity.INTENT_WEATHER_ID to resultItem.id + activity?.startActivity( + Intent(context, DetailActivity::class.java).putExtra( + DetailActivity.INTENT_WEATHER_ID, + resultItem.id + ) ) } private fun showWeatherItemList(newList: List) { - val adapter: WeatherListAdapter = weatherList.adapter as WeatherListAdapter + val adapter: WeatherListAdapter = binding.weatherList.adapter as WeatherListAdapter adapter.list = newList adapter.notifyDataSetChanged() } diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherViewModel.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherViewModel.kt index 8313308..8f00b69 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherViewModel.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/WeatherViewModel.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.view.weather -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.util.coroutines.SchedulerProvider diff --git a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/list/WeatherListAdapter.kt b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/list/WeatherListAdapter.kt index 28ae366..409fe51 100644 --- a/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/list/WeatherListAdapter.kt +++ b/examples/android-weather-app/src/main/kotlin/fr/ekito/myweatherapp/view/weather/list/WeatherListAdapter.kt @@ -1,25 +1,21 @@ package fr.ekito.myweatherapp.view.weather.list import android.content.Context -import android.support.v7.widget.RecyclerView import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.TextView -import com.joanzapata.iconify.widget.IconTextView -import fr.ekito.myweatherapp.R +import androidx.recyclerview.widget.RecyclerView +import fr.ekito.myweatherapp.databinding.ItemWeatherBinding import fr.ekito.myweatherapp.domain.entity.getColorFromCode class WeatherListAdapter( - val context: Context, + private val context: Context, var list: List, private val onDetailSelected: (WeatherItem) -> Unit ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeatherResultHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.item_weather, parent, false) - return WeatherResultHolder(view) + val binding = ItemWeatherBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return WeatherResultHolder(binding) } override fun onBindViewHolder(holder: WeatherResultHolder, position: Int) { @@ -28,22 +24,22 @@ class WeatherListAdapter( override fun getItemCount() = list.size - inner class WeatherResultHolder(item: View) : RecyclerView.ViewHolder(item) { - private val weatherItemLayout = item.findViewById(R.id.weatherItemLayout) - private val weatherItemDay = item.findViewById(R.id.weatheItemrDay) - private val weatherItemIcon = item.findViewById(R.id.weatherItemIcon) - + inner class WeatherResultHolder( + private val binding: ItemWeatherBinding + ) : RecyclerView.ViewHolder(binding.root) { fun display( dailyForecastModel: WeatherItem, context: Context, onClick: (WeatherItem) -> Unit ) { - weatherItemLayout.setOnClickListener { onClick(dailyForecastModel) } - weatherItemDay.text = dailyForecastModel.day - weatherItemIcon.text = dailyForecastModel.icon - val color = context.getColorFromCode(dailyForecastModel) - weatherItemDay.setTextColor(color) - weatherItemIcon.setTextColor(color) + with(binding) { + weatherItemLayout.setOnClickListener { onClick(dailyForecastModel) } + weatherItemDay.text = dailyForecastModel.day + weatherItemIcon.text = dailyForecastModel.icon + val color = context.getColorFromCode(dailyForecastModel) + weatherItemDay.setTextColor(color) + weatherItemIcon.setTextColor(color) + } } } diff --git a/examples/android-weather-app/src/main/res/layout/activity_detail.xml b/examples/android-weather-app/src/main/res/layout/activity_detail.xml index 68579d2..a5b30cf 100644 --- a/examples/android-weather-app/src/main/res/layout/activity_detail.xml +++ b/examples/android-weather-app/src/main/res/layout/activity_detail.xml @@ -1,5 +1,5 @@ - - - - + diff --git a/examples/android-weather-app/src/main/res/layout/fragment_result_header.xml b/examples/android-weather-app/src/main/res/layout/fragment_result_header.xml index e314753..a927739 100644 --- a/examples/android-weather-app/src/main/res/layout/fragment_result_header.xml +++ b/examples/android-weather-app/src/main/res/layout/fragment_result_header.xml @@ -1,5 +1,6 @@ + app:tint="@color/icons_black" + android:contentDescription="@string/city" /> @@ -55,7 +57,7 @@ android:layout_height="wrap_content" android:layout_margin="24dp" android:gravity="center" - android:text="Day" + android:text="@string/day" android:textSize="21sp" android:textColor="@color/t_white" /> @@ -65,7 +67,7 @@ android:layout_height="wrap_content" android:layout_margin="8dp" android:gravity="center" - android:text="Icon" + android:text="@string/icon" android:textColor="@color/t_white" android:textSize="92sp" /> @@ -76,7 +78,7 @@ android:layout_height="wrap_content" android:layout_margin="16dp" android:gravity="center" - android:text="Complete Text" + android:text="@string/complete_text" android:textColor="@color/t_white" /> @@ -101,8 +103,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" - android:gravity="center|left" - android:text="Day" + android:gravity="center|start" + android:text="@string/day" android:textColor="@color/t_white" /> diff --git a/examples/android-weather-app/src/main/res/layout/fragment_result_list.xml b/examples/android-weather-app/src/main/res/layout/fragment_result_list.xml index 9454c64..3eb4cac 100644 --- a/examples/android-weather-app/src/main/res/layout/fragment_result_list.xml +++ b/examples/android-weather-app/src/main/res/layout/fragment_result_list.xml @@ -6,7 +6,7 @@ android:gravity="center" android:orientation="vertical"> - diff --git a/examples/android-weather-app/src/main/res/menu/menu_main.xml b/examples/android-weather-app/src/main/res/menu/menu_main.xml index 065b28f..f16ecb0 100644 --- a/examples/android-weather-app/src/main/res/menu/menu_main.xml +++ b/examples/android-weather-app/src/main/res/menu/menu_main.xml @@ -1,2 +1,2 @@ + tools:context="fr.ekito.myweatherapp.MainActivity" /> diff --git a/examples/android-weather-app/src/main/res/values/strings.xml b/examples/android-weather-app/src/main/res/values/strings.xml index fc84f4a..d248f88 100644 --- a/examples/android-weather-app/src/main/res/values/strings.xml +++ b/examples/android-weather-app/src/main/res/values/strings.xml @@ -7,4 +7,11 @@ CANCEL Retry Loading location + Icon + Day + Complete Text + City + Wind + Temp + Humidity diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/ModuleCheckTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/ModuleCheckTest.kt index c4bd8ce..dfbe458 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/ModuleCheckTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/ModuleCheckTest.kt @@ -9,8 +9,8 @@ import fr.ekito.myweatherapp.view.detail.DetailViewModel import org.junit.Test import org.koin.android.ext.koin.androidContext import org.koin.core.logger.Level -import org.koin.core.parameter.parametersOf import org.koin.dsl.koinApplication +import org.koin.fileProperties import org.koin.test.KoinTest import org.koin.test.check.checkModules import org.mockito.Mockito.mock @@ -30,7 +30,7 @@ class ModuleCheckTest : KoinTest { androidContext(mockedAndroidContext) modules(onlineWeatherApp) }.checkModules { - create { parametersOf(viewModelId) } + withParameter { viewModelId } } } @@ -40,7 +40,7 @@ class ModuleCheckTest : KoinTest { androidContext(mockedAndroidContext) modules(offlineWeatherApp) }.checkModules { - create { parametersOf(viewModelId) } + withParameter { viewModelId } } } @@ -51,7 +51,7 @@ class ModuleCheckTest : KoinTest { androidContext(mockedAndroidContext) modules(testWeatherApp) }.checkModules { - create { parametersOf(viewModelId) } + withParameter { viewModelId } } } @@ -61,7 +61,7 @@ class ModuleCheckTest : KoinTest { androidContext(mockedAndroidContext) modules(roomWeatherApp) }.checkModules { - create { parametersOf(viewModelId) } + withParameter { viewModelId } } } } diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/di/test_modules.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/di/test_modules.kt index 6ccf4f8..b2308fb 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/di/test_modules.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/di/test_modules.kt @@ -12,7 +12,7 @@ import org.koin.dsl.module /** * Local java json repository */ -val localJavaDatasourceModule = module(override = true) { +val localJavaDatasourceModule = module { single { JavaReader() } single { FileDataSource(get(), false) } } @@ -20,7 +20,7 @@ val localJavaDatasourceModule = module(override = true) { /** * Test Rx */ -val testRxModule = module(override = true) { +val testRxModule = module { // provided components single { TestSchedulerProvider() } } diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/integration/WeatherRepositoryTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/integration/WeatherRepositoryTest.kt index 2adbb2d..044a568 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/integration/WeatherRepositoryTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/integration/WeatherRepositoryTest.kt @@ -2,8 +2,10 @@ package fr.ekito.myweatherapp.integration import fr.ekito.myweatherapp.di.testWeatherApp import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository -import junit.framework.Assert +import fr.ekito.myweatherapp.util.awaitTerminalEvent import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotSame import org.junit.Before import org.junit.Test import org.koin.core.context.startKoin @@ -13,7 +15,7 @@ import org.koin.test.inject class WeatherRepositoryTest : KoinTest { - val repository by inject() + private val repository by inject() val location = "Paris" @@ -43,8 +45,8 @@ class WeatherRepositoryTest : KoinTest { val l2 = repository.getWeather("Toulouse").blockingGet() val l3 = repository.getWeather().blockingGet() - Assert.assertEquals(l3, l2) - Assert.assertNotSame(l1, l2) + assertEquals(l3, l2) + assertNotSame(l1, l2) } @Test diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/MockedData.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/MockedData.kt index 6a24478..c9f1a20 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/MockedData.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/MockedData.kt @@ -10,7 +10,7 @@ import fr.ekito.myweatherapp.domain.entity.Wind */ object MockedData { - val location = "Location" + const val location = "Location" val mockList = listOf( DailyForecast( diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/DetailViewModelMockTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/DetailViewModelMockTest.kt index 191af97..4bcfd9f 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/DetailViewModelMockTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/DetailViewModelMockTest.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.mock.mvvm -import android.arch.core.executor.testing.InstantTaskExecutorRule -import android.arch.lifecycle.Observer +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.util.TestSchedulerProvider @@ -9,7 +9,7 @@ import fr.ekito.myweatherapp.view.Failed import fr.ekito.myweatherapp.view.Loading import fr.ekito.myweatherapp.view.ViewModelState import fr.ekito.myweatherapp.view.detail.DetailViewModel -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -24,16 +24,18 @@ import org.mockito.MockitoAnnotations class DetailViewModelMockTest { - lateinit var detailViewModel: DetailViewModel + private lateinit var detailViewModel: DetailViewModel + @Mock lateinit var view: Observer + @Mock lateinit var repository: DailyForecastRepository @get:Rule val rule = InstantTaskExecutorRule() - val id = "ID" + private val id = "ID" @Before fun before() { @@ -65,7 +67,7 @@ class DetailViewModelMockTest { } @Test - fun testGeLasttWeatherFailed() { + fun testGeLastWeatherFailed() { val error = Throwable("Got error") given(repository.getWeatherDetail(id)).willReturn(Single.error(error)) diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/SplashViewModelMockTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/SplashViewModelMockTest.kt index e240c88..9c15027 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/SplashViewModelMockTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/SplashViewModelMockTest.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.mock.mvvm -import android.arch.core.executor.testing.InstantTaskExecutorRule -import android.arch.lifecycle.Observer +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import fr.ekito.myweatherapp.domain.entity.DailyForecast import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.util.TestSchedulerProvider @@ -10,7 +10,7 @@ import fr.ekito.myweatherapp.view.Pending import fr.ekito.myweatherapp.view.Success import fr.ekito.myweatherapp.view.ViewModelEvent import fr.ekito.myweatherapp.view.splash.SplashViewModel -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -23,7 +23,7 @@ import org.mockito.MockitoAnnotations class SplashViewModelMockTest { - lateinit var viewModel: SplashViewModel + private lateinit var viewModel: SplashViewModel @Mock lateinit var view: Observer @@ -64,7 +64,7 @@ class SplashViewModelMockTest { } @Test - fun testGetLasttWeatherFailed() { + fun testGetLastWeatherFailed() { val error = Throwable("Got an error") given(repository.getWeather()).willReturn(Single.error(error)) diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherHeaderViewModelMockTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherHeaderViewModelMockTest.kt index d0f35b3..8fa408f 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherHeaderViewModelMockTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherHeaderViewModelMockTest.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.mock.mvvm -import android.arch.core.executor.testing.InstantTaskExecutorRule -import android.arch.lifecycle.Observer +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.mock.MockedData.mockList import fr.ekito.myweatherapp.util.MockitoHelper @@ -11,7 +11,7 @@ import fr.ekito.myweatherapp.view.Loading import fr.ekito.myweatherapp.view.ViewModelEvent import fr.ekito.myweatherapp.view.ViewModelState import fr.ekito.myweatherapp.view.weather.WeatherViewModel -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -25,7 +25,7 @@ import org.mockito.MockitoAnnotations class WeatherHeaderViewModelMockTest { - lateinit var viewModel: WeatherViewModel + private lateinit var viewModel: WeatherViewModel @Mock lateinit var viewStates: Observer diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherListViewModelMockTest.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherListViewModelMockTest.kt index b7feb38..d3ecb3e 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherListViewModelMockTest.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/mock/mvvm/WeatherListViewModelMockTest.kt @@ -1,7 +1,7 @@ package fr.ekito.myweatherapp.mock.mvvm -import android.arch.core.executor.testing.InstantTaskExecutorRule -import android.arch.lifecycle.Observer +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer import fr.ekito.myweatherapp.domain.repository.DailyForecastRepository import fr.ekito.myweatherapp.mock.MockedData.mockList import fr.ekito.myweatherapp.util.TestSchedulerProvider @@ -9,7 +9,7 @@ import fr.ekito.myweatherapp.view.Failed import fr.ekito.myweatherapp.view.Loading import fr.ekito.myweatherapp.view.ViewModelState import fr.ekito.myweatherapp.view.weather.WeatherViewModel -import io.reactivex.Single +import io.reactivex.rxjava3.core.Single import org.junit.Assert import org.junit.Before import org.junit.Rule @@ -23,9 +23,11 @@ import org.mockito.MockitoAnnotations class WeatherListViewModelMockTest { - lateinit var viewModel: WeatherViewModel + private lateinit var viewModel: WeatherViewModel + @Mock lateinit var view: Observer + @Mock lateinit var repository: DailyForecastRepository diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/RxTestExt.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/RxTestExt.kt new file mode 100644 index 0000000..f0dd905 --- /dev/null +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/RxTestExt.kt @@ -0,0 +1,13 @@ +package fr.ekito.myweatherapp.util + +import io.reactivex.rxjava3.observers.TestObserver + +fun TestObserver.awaitTerminalEvent(): Boolean { + return try { + this.await() + true + } catch (ex: InterruptedException) { + Thread.currentThread().interrupt() + false + } +} \ No newline at end of file diff --git a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/TestSchedulerProvider.kt b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/TestSchedulerProvider.kt index 59d182f..a24b9b1 100644 --- a/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/TestSchedulerProvider.kt +++ b/examples/android-weather-app/src/test/java/fr/ekito/myweatherapp/util/TestSchedulerProvider.kt @@ -1,12 +1,13 @@ package fr.ekito.myweatherapp.util -import io.reactivex.schedulers.Schedulers +import io.reactivex.rxjava3.schedulers.Schedulers import fr.ekito.myweatherapp.util.coroutines.SchedulerProvider +import io.reactivex.rxjava3.core.Scheduler class TestSchedulerProvider : SchedulerProvider { - override fun io() = Schedulers.trampoline() + override fun io(): Scheduler = Schedulers.trampoline() - override fun ui() = Schedulers.trampoline() + override fun ui(): Scheduler = Schedulers.trampoline() - override fun computation() = Schedulers.trampoline() + override fun computation(): Scheduler = Schedulers.trampoline() } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 730b81c..0a76a5f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,3 +24,7 @@ org.gradle.configureondemand=false # Kotlin kotlin.incremental=true org.gradle.caching=true + +# Enable AndroidX +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/versions-android.gradle b/gradle/versions-android.gradle index 0299318..c7ada60 100644 --- a/gradle/versions-android.gradle +++ b/gradle/versions-android.gradle @@ -1,13 +1,11 @@ ext { // Sdk and tools - android_min_version = 14 - android_target_version = 28 + android_min_version = 21 + android_target_version = 31 - android_gradle_version = "3.2.1" - android_build_tools_version = '28.0.3' - support_lib_version = "28.0.0" - androidx_lib_version = "1.0.2" + android_gradle_version = '7.0.3' + android_build_tools_version = '30.0.3' - android_arch_version = '1.1.1' - androidx_arch_version = '2.0.0-rc01' + arch_version = "2.1.0" + lifecycle_version = '2.4.0' } \ No newline at end of file diff --git a/gradle/versions-examples.gradle b/gradle/versions-examples.gradle index 1bf2d3b..863235e 100644 --- a/gradle/versions-examples.gradle +++ b/gradle/versions-examples.gradle @@ -1,13 +1,12 @@ ext { // Android Support - support_lib_version = "28.0.0" - constraint_layout_version = "1.1.3" - room_version = "1.1.1" + constraint_layout_version = "2.1.1" + room_version = "2.3.0" - leak_canary_version = "1.6.1" + leak_canary_version = "2.7" anko_version='0.10.4' - okhttp_version = '3.8.1' - retrofit_version = '2.2.0' - rxjava_version = '2.1.7' + okhttp_version = '4.9.1' + retrofit_version = '2.9.0' + rxjava_version = '3.1.2' } \ No newline at end of file diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 4d959c4..0218079 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -1,10 +1,10 @@ ext { // Koin - koin_version = '2.0.1' + koin_version = '3.1.3' // Kotlin - kotlin_version = '1.3.21' - coroutines_version = "1.0.0" + kotlin_version = '1.5.31' + coroutines_version = "1.5.2" // Dokka dokka_version = '0.9.16' @@ -19,8 +19,8 @@ ext { // spark_kotlin_version = '1.0.0-alpha' // Test - junit_version = "4.12" - mockito_version = "2.21.0" + junit_version = "4.13.2" + mockito_version = "2.23.4" asciidoctor_version = "1.5.3" asciidoctor_pdf_version = "1.5.0-alpha.15" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0772cac..0f80bbf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Nov 13 18:16:32 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip diff --git a/gradlew b/gradlew index 9d82f78..cccdd3d 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 597e21c..f955316 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -5,17 +5,17 @@ @rem @rem ########################################################################## -@rem Set local modulePath for the variables with windows NT shell +@rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line @@ -75,7 +69,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end -@rem End local modulePath for the variables with windows NT shell +@rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail