From ff673e07f94194e1107c791cc96cf70fe740ef02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petrus=20Nguy=E1=BB=85n=20Th=C3=A1i=20H=E1=BB=8Dc?= Date: Mon, 6 Apr 2020 19:44:13 +0700 Subject: [PATCH] update(dependencies) --- .idea/codeStyles/codeStyleConfig.xml | 1 - app/build.gradle | 34 +++---- .../com/hoc/flowmvi/FlowBinding+Exts+Utils.kt | 58 +++++------ .../hoc/flowmvi/data/UserRepositoryImpl.kt | 2 +- .../java/com/hoc/flowmvi/koin/DomainModule.kt | 14 +-- .../com/hoc/flowmvi/ui/add/AddActivity.kt | 46 ++++----- .../com/hoc/flowmvi/ui/add/AddContract.kt | 8 +- .../main/java/com/hoc/flowmvi/ui/add/AddVM.kt | 99 ++++++++++--------- .../java/com/hoc/flowmvi/ui/main/MainVM.kt | 6 +- build.gradle | 6 +- 10 files changed, 142 insertions(+), 132 deletions(-) diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 6e6eec11..a55e7a17 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,6 +1,5 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ba5aac46..52ab6183 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,41 +38,41 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' + implementation 'androidx.appcompat:appcompat:1.2.0-beta01' + implementation 'androidx.core:core-ktx:1.3.0-beta01' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' - implementation 'com.google.android.material:material:1.2.0-alpha04' + implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha02' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01' + implementation 'com.google.android.material:material:1.2.0-alpha05' // viewModelScope - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-alpha01' // lifecycleScope - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha01' // Extensions for LiveData - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-alpha01' // retrofit2 - implementation 'com.squareup.retrofit2:retrofit:2.7.1' - implementation 'com.squareup.retrofit2:converter-moshi:2.7.1' - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1' + implementation 'com.squareup.retrofit2:retrofit:2.8.1' + implementation 'com.squareup.retrofit2:converter-moshi:2.8.1' + implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0' // moshi - implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' + implementation 'com.squareup.moshi:moshi-kotlin:1.9.2' // coroutines - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5' // koin - implementation 'org.koin:koin-androidx-viewmodel:2.0.0' + implementation 'org.koin:koin-androidx-viewmodel:2.1.5' // coil - implementation 'io.coil-kt:coil:0.8.0' + implementation 'io.coil-kt:coil:0.9.5' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/app/src/main/java/com/hoc/flowmvi/FlowBinding+Exts+Utils.kt b/app/src/main/java/com/hoc/flowmvi/FlowBinding+Exts+Utils.kt index e0b7fd42..b97ae274 100644 --- a/app/src/main/java/com/hoc/flowmvi/FlowBinding+Exts+Utils.kt +++ b/app/src/main/java/com/hoc/flowmvi/FlowBinding+Exts+Utils.kt @@ -39,8 +39,9 @@ fun View.clicks(): Flow { } @ExperimentalCoroutinesApi +@CheckResult fun EditText.textChanges(): Flow { - return callbackFlow { + return callbackFlow { val listener = object : TextWatcher { override fun afterTextChanged(s: Editable?) = Unit override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit @@ -55,7 +56,7 @@ fun EditText.textChanges(): Flow { @ExperimentalCoroutinesApi fun Flow.flatMapFirst(transform: suspend (value: T) -> Flow): Flow = - map(transform).flattenFirst() + map(transform).flattenFirst() @ExperimentalCoroutinesApi fun Flow>.flattenFirst(): Flow = channelFlow { @@ -105,47 +106,48 @@ fun Flow.withLatestFrom(other: Flow, transform: suspend (A, B) - fun Context.toast(text: CharSequence) = Toast.makeText(this, text, Toast.LENGTH_SHORT).show() +@ExperimentalCoroutinesApi suspend fun main() { (1..2000).asFlow() - .onEach { delay(50) } - .flatMapFirst { v -> - flow { - delay(500) - emit(v) + .onEach { delay(50) } + .flatMapFirst { v -> + flow { + delay(500) + emit(v) + } } - } - .onEach { println("[*] $it") } - .catch { println("Error $it") } - .collect() + .onEach { println("[*] $it") } + .catch { println("Error $it") } + .collect() } class SwipeLeftToDeleteCallback(context: Context, private val onSwipedCallback: (Int) -> Unit) : - ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { + ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { private val background: ColorDrawable = ColorDrawable(Color.parseColor("#f44336")) private val iconDelete = - ContextCompat.getDrawable(context, R.drawable.ic_baseline_delete_white_24)!! + ContextCompat.getDrawable(context, R.drawable.ic_baseline_delete_white_24)!! override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder ) = false override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - val position = viewHolder.adapterPosition + val position = viewHolder.bindingAdapterPosition if (position != RecyclerView.NO_POSITION) { onSwipedCallback(position) } } override fun onChildDraw( - c: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean ) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) val itemView = viewHolder.itemView @@ -161,10 +163,10 @@ class SwipeLeftToDeleteCallback(context: Context, private val onSwipedCallback: iconDelete.setBounds(iconLeft, iconTop, iconRight, iconBottom) background.setBounds( - itemView.right + dX.toInt() - 8, - itemView.top, - itemView.right, - itemView.bottom + itemView.right + dX.toInt() - 8, + itemView.top, + itemView.right, + itemView.bottom ) } else -> background.setBounds(0, 0, 0, 0) diff --git a/app/src/main/java/com/hoc/flowmvi/data/UserRepositoryImpl.kt b/app/src/main/java/com/hoc/flowmvi/data/UserRepositoryImpl.kt index 4c54f1c4..36965289 100644 --- a/app/src/main/java/com/hoc/flowmvi/data/UserRepositoryImpl.kt +++ b/app/src/main/java/com/hoc/flowmvi/data/UserRepositoryImpl.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext -@FlowPreview @ExperimentalCoroutinesApi class UserRepositoryImpl( private val userApiService: UserApiService, @@ -39,6 +38,7 @@ class UserRepositoryImpl( } } + @FlowPreview override fun getUsers(): Flow> { return flow { val initial = getUsersFromRemote() diff --git a/app/src/main/java/com/hoc/flowmvi/koin/DomainModule.kt b/app/src/main/java/com/hoc/flowmvi/koin/DomainModule.kt index 9fde27f9..6b89b253 100644 --- a/app/src/main/java/com/hoc/flowmvi/koin/DomainModule.kt +++ b/app/src/main/java/com/hoc/flowmvi/koin/DomainModule.kt @@ -18,23 +18,23 @@ import org.koin.dsl.module @ExperimentalCoroutinesApi @FlowPreview val domainModule = module { - single { CoroutineDispatchersImpl() as CoroutineDispatchers } + single { CoroutineDispatchersImpl() } - single { + single { UserRepositoryImpl( get(), get(), responseToDomain = get(), domainToResponse = get(), domainToBody = get() - ) as UserRepository + ) } - single { GetUsersUseCase(get()) } + factory { GetUsersUseCase(get()) } - single { RefreshGetUsersUseCase(get()) } + factory { RefreshGetUsersUseCase(get()) } - single { RemoveUserUseCase(get()) } + factory { RemoveUserUseCase(get()) } - single { AddUserUseCase(get()) } + factory { AddUserUseCase(get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt b/app/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt index 5c29b0d1..d707bd72 100644 --- a/app/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt +++ b/app/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt @@ -43,14 +43,14 @@ class AddActivity : AppCompatActivity(), View { // observe view model addVM.viewState.observe(this, Observer { render(it ?: return@Observer) }) addVM.singleEvent.observe( - this, - Observer { handleSingleEvent(it?.getContentIfNotHandled() ?: return@Observer) } + this, + Observer { handleSingleEvent(it?.getContentIfNotHandled() ?: return@Observer) } ) // pass view intent to view model intents() - .onEach { addVM.processIntent(it) } - .launchIn(lifecycleScope) + .onEach { addVM.processIntent(it) } + .launchIn(lifecycleScope) } private fun handleSingleEvent(event: SingleEvent) { @@ -102,25 +102,25 @@ class AddActivity : AppCompatActivity(), View { override fun intents(): Flow { return merge( - addBinding - .emailEditText - .editText!! - .textChanges() - .map { ViewIntent.EmailChanged(it?.toString()) }, - addBinding - .firstNameEditText - .editText!! - .textChanges() - .map { ViewIntent.FirstNameChanged(it?.toString()) }, - addBinding - .lastNameEditText - .editText!! - .textChanges() - .map { ViewIntent.LastNameChanged(it?.toString()) }, - addBinding - .addButton - .clicks() - .map { ViewIntent.Submit } + addBinding + .emailEditText + .editText!! + .textChanges() + .map { ViewIntent.EmailChanged(it?.toString()) }, + addBinding + .firstNameEditText + .editText!! + .textChanges() + .map { ViewIntent.FirstNameChanged(it?.toString()) }, + addBinding + .lastNameEditText + .editText!! + .textChanges() + .map { ViewIntent.LastNameChanged(it?.toString()) }, + addBinding + .addButton + .clicks() + .map { ViewIntent.Submit } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/hoc/flowmvi/ui/add/AddContract.kt b/app/src/main/java/com/hoc/flowmvi/ui/add/AddContract.kt index 40863de9..3569ed61 100644 --- a/app/src/main/java/com/hoc/flowmvi/ui/add/AddContract.kt +++ b/app/src/main/java/com/hoc/flowmvi/ui/add/AddContract.kt @@ -15,13 +15,13 @@ interface AddContract { } data class ViewState( - val errors: Set, - val isLoading: Boolean + val errors: Set, + val isLoading: Boolean ) { companion object { fun initial() = ViewState( - errors = emptySet(), - isLoading = false + errors = emptySet(), + isLoading = false ) } } diff --git a/app/src/main/java/com/hoc/flowmvi/ui/add/AddVM.kt b/app/src/main/java/com/hoc/flowmvi/ui/add/AddVM.kt index 1a89c2b9..546dd046 100644 --- a/app/src/main/java/com/hoc/flowmvi/ui/add/AddVM.kt +++ b/app/src/main/java/com/hoc/flowmvi/ui/add/AddVM.kt @@ -30,75 +30,78 @@ class AddVM(private val addUser: AddUserUseCase) : ViewModel() { init { _intentChannel - .asFlow() - .toPartialStateChangesFlow() - .sendSingleEvent() - .scan(initialVS) { state, change -> change.reduce(state) } - .onEach { _viewStateD.value = it } - .catch { } - .launchIn(viewModelScope) + .asFlow() + .toPartialStateChangesFlow() + .sendSingleEvent() + .scan(initialVS) { state, change -> change.reduce(state) } + .onEach { _viewStateD.value = it } + .catch { } + .launchIn(viewModelScope) } private fun Flow.sendSingleEvent(): Flow { return onEach { change -> _eventD.value = Event( - when (change) { - is PartialStateChange.ErrorsChanged -> return@onEach - PartialStateChange.AddUser.Loading -> return@onEach - is PartialStateChange.AddUser.AddUserSuccess -> SingleEvent.AddUserSuccess(change.user) - is PartialStateChange.AddUser.AddUserFailure -> SingleEvent.AddUserFailure( - change.user, - change.throwable - ) - } + when (change) { + is PartialStateChange.ErrorsChanged -> return@onEach + PartialStateChange.AddUser.Loading -> return@onEach + is PartialStateChange.AddUser.AddUserSuccess -> SingleEvent.AddUserSuccess(change.user) + is PartialStateChange.AddUser.AddUserFailure -> SingleEvent.AddUserFailure( + change.user, + change.throwable + ) + } ) } } private fun Flow.toPartialStateChangesFlow(): Flow { val emailErrors = filterIsInstance() - .map { it.email } - .map { validateEmail(it) to it } + .map { it.email } + .map { validateEmail(it) to it } val firstNameErrors = filterIsInstance() - .map { it.firstName } - .map { validateFirstName(it) to it } + .map { it.firstName } + .map { validateFirstName(it) to it } val lastNameErrors = filterIsInstance() - .map { it.lastName } - .map { validateLastName(it) to it } + .map { it.lastName } + .map { validateLastName(it) to it } val userFormFlow = - combine(emailErrors, firstNameErrors, lastNameErrors) { email, firstName, lastName -> - UserForm( - errors = email.first + firstName.first + lastName.first, - user = User( - firstName = firstName.second ?: "", - email = email.second ?: "", - lastName = lastName.second ?: "", - id = "", - avatar = "" + combine(emailErrors, firstNameErrors, lastNameErrors) { email, firstName, lastName -> + UserForm( + errors = email.first + firstName.first + lastName.first, + user = User( + firstName = firstName.second ?: "", + email = email.second ?: "", + lastName = lastName.second ?: "", + id = "", + avatar = "" + ) ) - ) - } + } val addUserChanges = filterIsInstance() - .withLatestFrom(userFormFlow) { _, userForm -> userForm } - .filter { it.errors.isEmpty() } - .map { it.user } - .flatMapFirst { user -> - flow { emit(addUser(user)) } - .map { PartialStateChange.AddUser.AddUserSuccess(user) as PartialStateChange.AddUser } - .onStart { emit(PartialStateChange.AddUser.Loading) } - .catch { emit(PartialStateChange.AddUser.AddUserFailure(user, it)) } - } + .withLatestFrom(userFormFlow) { _, userForm -> userForm } + .filter { it.errors.isEmpty() } + .map { it.user } + .flatMapFirst { user -> + flow { emit(addUser(user)) } + .map { + @Suppress("USELESS_CAST") + PartialStateChange.AddUser.AddUserSuccess(user) as PartialStateChange.AddUser + } + .onStart { emit(PartialStateChange.AddUser.Loading) } + .catch { emit(PartialStateChange.AddUser.AddUserFailure(user, it)) } + } return merge( - userFormFlow - .map { it.errors } - .map { PartialStateChange.ErrorsChanged(it) }, - addUserChanges + userFormFlow + .map { it.errors } + .map { PartialStateChange.ErrorsChanged(it) }, + addUserChanges ) } @@ -107,8 +110,8 @@ class AddVM(private val addUser: AddUserUseCase) : ViewModel() { const val MIN_LENGTH_LAST_NAME = 3 private data class UserForm( - val errors: Set, - val user: User + val errors: Set, + val user: User ) fun validateFirstName(firstName: String?): Set { diff --git a/app/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt b/app/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt index 87471c7d..c33abd95 100644 --- a/app/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt +++ b/app/src/main/java/com/hoc/flowmvi/ui/main/MainVM.kt @@ -72,13 +72,17 @@ class MainVM( .onEach { Log.d("###", "[MAIN_VM] Emit users.size=${it.size}") } .map { val items = it.map(::UserItem) + @Suppress("USELESS_CAST") PartialChange.GetUser.Data(items) as PartialChange.GetUser } .onStart { emit(PartialChange.GetUser.Loading) } .catch { emit(PartialChange.GetUser.Error(it)) } val refreshChanges = flow { emit(refreshGetUsersUseCase()) } - .map { PartialChange.Refresh.Success as PartialChange.Refresh } + .map { + @Suppress("USELESS_CAST") + PartialChange.Refresh.Success as PartialChange.Refresh + } .onStart { emit(PartialChange.Refresh.Loading) } .catch { emit(PartialChange.Refresh.Failure(it)) } diff --git a/build.gradle b/build.gradle index e58081e2..cf7a39c3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.61' + ext.kotlin_version = '1.4-M1' repositories { google() jcenter() + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.0-rc02' + classpath 'com.android.tools.build:gradle:3.6.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -20,6 +21,7 @@ allprojects { google() jcenter() mavenCentral() + maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } }