Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Migrate LiveData to Flow #68

Merged
merged 12 commits into from
Apr 8, 2022
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.goooler.demoapp.base.core

import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.ViewModel

abstract class BaseViewModel : ViewModel(), DefaultLifecycleObserver
abstract class BaseViewModel : ViewModel()
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ import androidx.fragment.app.commit
import androidx.fragment.app.findFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.findViewTreeLifecycleOwner
import java.io.File
Expand All @@ -66,34 +64,6 @@ import kotlinx.coroutines.withContext

// ---------------------Types-------------------------------//

typealias BooleanLiveData = LiveData<Boolean>

typealias IntLiveData = LiveData<Int>

typealias LongLiveData = LiveData<Long>

typealias DoubleLiveData = LiveData<Double>

typealias FloatLiveData = LiveData<Float>

typealias StringLiveData = LiveData<String>

typealias ListLiveData<T> = LiveData<List<T>>

typealias MutableBooleanLiveData = MutableLiveData<Boolean>

typealias MutableIntLiveData = MutableLiveData<Int>

typealias MutableLongLiveData = MutableLiveData<Long>

typealias MutableDoubleLiveData = MutableLiveData<Double>

typealias MutableFloatLiveData = MutableLiveData<Float>

typealias MutableStringLiveData = MutableLiveData<String?>

typealias MutableListLiveData<T> = MutableLiveData<List<T>>

typealias ParamMap = HashMap<String, Any>

// ---------------------Any-------------------------------//
Expand All @@ -109,8 +79,6 @@ inline val isMainThread: Boolean get() = Looper.getMainLooper() == Looper.myLoop
fun <T : Any> unsafeLazy(initializer: () -> T): Lazy<T> =
lazy(LazyThreadSafetyMode.NONE, initializer)

fun <T> MutableLiveData<T>.asLiveData(): LiveData<T> = this

// ---------------------CharSequence-------------------------------//

operator fun String.times(@IntRange(from = 0) num: Int): String {
Expand Down
3 changes: 0 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ allprojects {
libs.androidX.collection.get().module.group -> useVersion(libs.versions.androidX.collection.get())
libs.androidX.core.get().module.group -> useVersion(libs.versions.androidX.core.get())
libs.androidX.fragment.get().module.group -> useVersion(libs.versions.androidX.fragment.get())
libs.androidX.lifecycle.liveData.get().module.group -> {
if (requested.name != "lifecycle-extensions") useVersion(libs.versions.androidX.lifecycle.get())
}
libs.gradlePlugin.kotlin.get().module.group -> useVersion(libs.versions.kotlin.get())
libs.square.okHttp.logInterceptor.get().module.group -> useVersion(libs.versions.square.okHttp.get())
else -> when {
Expand Down
3 changes: 0 additions & 3 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,13 @@ dependencies {

api(libs.androidX.collection)
api(libs.utils)
api(libs.rxJava3.java)
api(libs.rxJava3.android)

implementation(libs.bundles.androidX.room)
kapt(libs.androidX.room.compiler)

implementation(libs.bundles.coil)
implementation(libs.square.moshi)
implementation(libs.square.retrofit.moshi)
implementation(libs.square.retrofit.rxJava3)

debugImplementation(libs.chucker.debug)
releaseImplementation(libs.chucker.release)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ package io.goooler.demoapp.common.base.theme

import androidx.annotation.AnyThread
import io.goooler.demoapp.base.core.BaseViewModel
import io.goooler.demoapp.base.util.MutableBooleanLiveData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

abstract class BaseThemeViewModel : BaseViewModel(), ITheme {

val loading = MutableBooleanLiveData()
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> get() = _loading

@AnyThread
override fun showLoading() {
loading.postValue(true)
_loading.value = true
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current value of this state flow.
Setting a value that is equal to the previous one does nothing.
This property is thread-safe and can be safely updated from concurrent coroutines without external synchronization.

}

@AnyThread
override fun hideLoading() {
loading.postValue(false)
_loading.value = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import io.goooler.demoapp.common.util.AppUserInfoManager
import io.goooler.demoapp.common.util.JsonUtil
import okhttp3.OkHttpClient
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory

@SuppressLint("StaticFieldLeak")
Expand All @@ -31,11 +29,6 @@ object RetrofitHelper : BaseRetrofitHelper() {
RouterManager.goLogin(CommonApplication.app, true)
}

override fun Retrofit.Builder.addCallAdapterFactories(): Retrofit.Builder {
addCallAdapterFactory(RxJava3CallAdapterFactory.create())
return this
}

override fun OkHttpClient.Builder.addInterceptors(): OkHttpClient.Builder {
addInterceptor(HeaderInterceptor)
addNetworkInterceptor(ChuckerInterceptor.Builder(context).build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.viewbinding.ViewBinding
Expand All @@ -34,24 +35,21 @@ import com.blankj.utilcode.util.StringUtils
import com.blankj.utilcode.util.TimeUtils
import com.google.android.material.textfield.TextInputLayout
import com.scwang.smart.refresh.layout.SmartRefreshLayout
import io.goooler.demoapp.base.core.BaseViewModel
import io.goooler.demoapp.base.util.ToastUtil
import io.goooler.demoapp.common.BuildConfig
import io.goooler.demoapp.common.CommonApplication
import io.goooler.demoapp.common.base.theme.BaseThemeViewModel
import io.goooler.demoapp.common.base.theme.ITheme
import io.goooler.demoapp.common.type.SpKeys
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import java.lang.reflect.ParameterizedType
import java.math.BigDecimal
import java.util.Calendar
import java.util.Date
import kotlin.math.absoluteValue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

typealias DimensionUtil = SizeUtils
typealias SpHelper = SPUtils
Expand Down Expand Up @@ -181,23 +179,6 @@ fun Number.formatMoney(isYuan: Boolean = false, trans2W: Boolean = false, scale:
}
}

// ---------------------Rx-------------------------------//

fun <T : Any> Single<T>.subscribeOnIoThread(): Single<T> = subscribeOn(Schedulers.io())

fun <T : Any> Single<T>.observeOnMainThread(): Single<T> = observeOn(AndroidSchedulers.mainThread())

fun <T : Any> Single<T>.subscribeAndObserve(): Single<T> =
subscribeOnIoThread().observeOnMainThread()

fun <T : Any> Observable<T>.subscribeOnIoThread(): Observable<T> = subscribeOn(Schedulers.io())

fun <T : Any> Observable<T>.observeOnMainThread(): Observable<T> =
observeOn(AndroidSchedulers.mainThread())

fun <T : Any> Observable<T>.subscribeAndObserve(): Observable<T> =
subscribeOnIoThread().observeOnMainThread()

// ---------------------Res-------------------------------//

fun @receiver:DrawableRes Int.getDrawable(): Drawable? = try {
Expand Down Expand Up @@ -267,19 +248,14 @@ inline fun <reified T> DiffUtil.ItemCallback<T>.asConfig(): AsyncDifferConfig<T>

// ---------------------Fragment-------------------------------//

@MainThread
inline fun <reified V, reified VM : BaseViewModel> V.baseViewModels(): Lazy<VM>
where V : LifecycleOwner, V : ViewModelStoreOwner = lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(this)[VM::class.java].apply(lifecycle::addObserver)
}

Comment on lines -178 to -183
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up #75 (comment).

@MainThread
inline fun <reified V, reified VM : BaseThemeViewModel> V.themeViewModels(): Lazy<VM>
where V : LifecycleOwner, V : ViewModelStoreOwner, V : ITheme = lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(this)[VM::class.java].also {
lifecycle.addObserver(it)
it.loading.observe(this) { show ->
if (show) showLoading() else hideLoading()
lifecycleScope.launch {
it.loading.collectLatest { show ->
if (show) showLoading() else hideLoading()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package io.goooler.demoapp.detail.ui

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import io.goooler.demoapp.base.core.BaseActivity
import io.goooler.demoapp.common.util.baseViewModels
import io.goooler.demoapp.detail.vm.DetailViewModel

class RepoDetailActivity : BaseActivity() {

private val vm: DetailViewModel by baseViewModels()
private val vm: DetailViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -21,9 +21,8 @@ class RepoDetailActivity : BaseActivity() {
}

setContent {
val model = vm.repoDetailModel.observeAsState().value
?: throw IllegalArgumentException("RepoDetailModel has not been initialized")
val isRefreshing by vm.isRefreshing.observeAsState(false)
val model by vm.repoDetailModel.collectAsState()
val isRefreshing by vm.isRefreshing.collectAsState()
DetailPageWithSwipeRefresh(isRefreshing, vm::refresh, model, vm::fork)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
package io.goooler.demoapp.detail.vm

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import io.goooler.demoapp.base.core.BaseViewModel
import io.goooler.demoapp.base.util.MutableBooleanLiveData
import io.goooler.demoapp.common.network.RetrofitHelper
import io.goooler.demoapp.detail.model.RepoDetailModel
import io.goooler.demoapp.detail.repository.DetailRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class DetailViewModel : BaseViewModel() {

private val repository = DetailRepository(RetrofitHelper.create())
private var repoDetail = RepoDetailModel()
private val _repoDetailModel = MutableLiveData(repoDetail)

private val _repoDetailModel = MutableStateFlow(repoDetail)
private val _isRefreshing = MutableStateFlow(false)

lateinit var fullName: String
val repoDetailModel: LiveData<RepoDetailModel> = _repoDetailModel
val isRefreshing: MutableBooleanLiveData = MutableBooleanLiveData()

val repoDetailModel: StateFlow<RepoDetailModel>
get() = _repoDetailModel.asStateFlow()
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
Comment on lines +22 to +26
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up #57 (comment).


fun refresh() {
isRefreshing.value = true
viewModelScope.launch {
_isRefreshing.emit(true)
repository.getRepoDetail(fullName).let {
repoDetail = RepoDetailModel(
it.fullName,
Expand All @@ -34,7 +39,7 @@ class DetailViewModel : BaseViewModel() {
)
}
_repoDetailModel.value = repoDetail
isRefreshing.value = false
_isRefreshing.emit(false)
}
}

Expand Down
5 changes: 0 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ androidX-compose-tooling = { module = "androidx.compose.ui:ui-tooling", version.
androidX-compose-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidX-compose" }
androidX-compose-material = { module = "androidx.compose.material:material", version.ref = "androidX-compose" }
androidX-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidX-compose" }
androidX-compose-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "androidX-compose" }
androidX-compose-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "androidX-compose" }
androidX-compose-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidX-compose" }

Expand All @@ -59,7 +58,6 @@ androidX-core = { module = "androidx.core:core-ktx", version.ref = "androidX-cor
androidX-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidX-fragment" }
androidX-hilt = { module = "com.google.dagger:hilt-android", version.ref = "androidX-hilt" }
androidX-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "androidX-hilt" }
androidX-lifecycle-liveData = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidX-lifecycle" }
androidX-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "androidX-lifecycle" }
androidX-lifecycle-viewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidX-lifecycle" }
androidX-paging = "androidx.paging:paging-runtime:3.1.1"
Expand All @@ -77,7 +75,6 @@ square-okHttp-LogInterceptor = { module = "com.squareup.okhttp3:logging-intercep
square-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "square-retrofit" }
square-retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "square-retrofit" }
square-retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "square-retrofit" }
square-retrofit-rxJava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "square-retrofit" }

google-accompanist-swipeRefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" }
google-accompanist-systemUiController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
Expand Down Expand Up @@ -109,13 +106,11 @@ androidX-compose = [
"androidX-compose-tooling-preview",
"androidX-compose-material",
"androidX-compose-foundation",
"androidX-compose-livedata",
"androidX-compose-icons-core",
"androidX-compose-icons-extended"
]
androidX-lifecycle = [
"androidX-lifecycle-viewModel",
"androidX-lifecycle-liveData",
"androidX-lifecycle-service"
]
androidX-room = [
Expand Down
2 changes: 0 additions & 2 deletions gradle/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@
# Extra rules for R8 fullMode
-keep,allowobfuscation,allowshrinking class io.goooler.demoapp.common.base.binding.BaseBindingActivity
-keep,allowobfuscation,allowshrinking class * extends io.goooler.demoapp.common.base.binding.BaseBindingActivity
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Single
-keep,allowobfuscation,allowshrinking class io.reactivex.rxjava3.core.Observable
# TODO: Waiting for new retrofit release to remove these rules
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,14 @@ package io.goooler.demoapp.main.api

import io.goooler.demoapp.base.util.ParamMap
import io.goooler.demoapp.main.bean.MainRepoListBean
import io.reactivex.rxjava3.core.Single
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.QueryMap

interface MainCommonApi {

@GET("users/{user}/repos")
fun getRepoListWithRx(
@Path("user") user: String,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<MainRepoListBean>>

@GET("users/{user}/repos")
suspend fun getRepoListWithCr(
suspend fun getRepoList(
@Path("user") user: String,
@QueryMap params: ParamMap
): List<MainRepoListBean>
Expand Down
Loading