Skip to content

Commit 08a85cc

Browse files
committed
update(deps) + refactor
1 parent 1c1bf5e commit 08a85cc

File tree

8 files changed

+120
-70
lines changed

8 files changed

+120
-70
lines changed

app/build.gradle

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,31 @@ android {
3131
jvmTarget = "1.8"
3232
}
3333

34-
viewBinding {
35-
enabled = true
34+
buildFeatures {
35+
viewBinding = true
3636
}
3737

3838
}
3939

4040
dependencies {
4141
implementation fileTree(dir: 'libs', include: ['*.jar'])
4242

43-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
43+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
44+
4445
implementation 'androidx.appcompat:appcompat:1.1.0'
45-
implementation 'androidx.core:core-ktx:1.2.0'
46+
implementation 'androidx.core:core-ktx:1.3.0'
4647
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
47-
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
48+
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'
4849
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
4950
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
50-
implementation 'androidx.fragment:fragment-ktx:1.2.3'
51+
implementation 'androidx.fragment:fragment-ktx:1.2.4'
5152
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
5253

5354
// coroutines
54-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
55-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
55+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
56+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
5657

57-
testImplementation 'junit:junit:4.12'
58+
testImplementation 'junit:junit:4.13'
5859
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
5960
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
6061
}

app/src/main/java/com/hoc/mergeadapter_sample/Data.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,17 @@ import kotlinx.coroutines.delay
44
import java.util.concurrent.atomic.AtomicBoolean
55
import java.util.concurrent.atomic.AtomicInteger
66

7+
//region Models
78
data class User(
89
val uid: Int,
910
val name: String,
1011
val email: String
1112
)
1213

13-
sealed class LoadingState {
14-
object Loading : LoadingState()
15-
data class Error(val throwable: Throwable) : LoadingState()
16-
}
17-
1814
object ApiError : Throwable(message = "Api error")
15+
//endregion
1916

17+
//region Fake api calling
2018
suspend fun getUsers(start: Int, limit: Int): List<User> {
2119
delay(2_000)
2220

@@ -34,4 +32,5 @@ suspend fun getUsers(start: Int, limit: Int): List<User> {
3432
}
3533

3634
private val count = AtomicInteger(0)
37-
private val throwError = AtomicBoolean(true)
35+
private val throwError = AtomicBoolean(true)
36+
//endregion

app/src/main/java/com/hoc/mergeadapter_sample/FooterAdapter.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import androidx.recyclerview.widget.RecyclerView
99
import com.hoc.mergeadapter_sample.databinding.ItemFooterBinding
1010

1111
class FooterAdapter(private val onRetry: () -> Unit) :
12-
ListAdapter<LoadingState, FooterAdapter.VH>(object : DiffUtil.ItemCallback<LoadingState>() {
13-
override fun areItemsTheSame(oldItem: LoadingState, newItem: LoadingState) = true
14-
override fun areContentsTheSame(oldItem: LoadingState, newItem: LoadingState) =
12+
ListAdapter<PlaceholderState, FooterAdapter.VH>(object :
13+
DiffUtil.ItemCallback<PlaceholderState>() {
14+
override fun areItemsTheSame(oldItem: PlaceholderState, newItem: PlaceholderState) = true
15+
override fun areContentsTheSame(oldItem: PlaceholderState, newItem: PlaceholderState) =
1516
oldItem == newItem
1617
}) {
1718

@@ -32,23 +33,24 @@ class FooterAdapter(private val onRetry: () -> Unit) :
3233
}
3334
}
3435

35-
fun bind(item: LoadingState) {
36+
fun bind(item: PlaceholderState) {
3637
when (item) {
37-
LoadingState.Loading -> {
38+
PlaceholderState.Loading -> {
3839
binding.run {
3940
buttonRetry.visibility = View.INVISIBLE
4041
textViewError.visibility = View.INVISIBLE
4142
progressBar.visibility = View.VISIBLE
4243
}
4344
}
44-
is LoadingState.Error -> {
45+
is PlaceholderState.Failure -> {
4546
binding.run {
4647
buttonRetry.visibility = View.VISIBLE
4748
textViewError.visibility = View.VISIBLE
4849
textViewError.text = item.throwable.message
4950
progressBar.visibility = View.INVISIBLE
5051
}
5152
}
53+
PlaceholderState.Idle -> error("Should not be here!")
5254
}
5355
}
5456
}

app/src/main/java/com/hoc/mergeadapter_sample/MainActivity.kt

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,12 @@ import com.hoc.mergeadapter_sample.databinding.ActivityMainBinding
1111
import kotlin.LazyThreadSafetyMode.NONE
1212

1313
class MainActivity : AppCompatActivity() {
14-
private val vm by viewModels<MainVM>()
14+
private val binding by lazy(NONE) { ActivityMainBinding.inflate(layoutInflater) }
15+
private val viewModel by viewModels<MainVM>(viewModelFactoryProducer)
1516

1617
private val userAdapter = UserAdapter()
1718
private val footerAdapter = FooterAdapter(this::onRetry)
1819

19-
private val binding by lazy(NONE) {
20-
ActivityMainBinding.inflate(layoutInflater)
21-
}
22-
2320
override fun onCreate(savedInstanceState: Bundle?) {
2421
super.onCreate(savedInstanceState)
2522
setContentView(binding.root)
@@ -29,28 +26,23 @@ class MainActivity : AppCompatActivity() {
2926
val linearLayoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
3027
layoutManager = linearLayoutManager
3128
adapter = MergeAdapter(userAdapter, footerAdapter)
29+
3230
addOnScrollListener(object : RecyclerView.OnScrollListener() {
3331
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
3432
if (dy > 0
3533
&& linearLayoutManager.findLastVisibleItemPosition() + VISIBLE_THRESHOLD >= linearLayoutManager.itemCount
3634
) {
37-
vm.loadNextPage()
35+
viewModel.loadNextPage()
3836
}
3937
}
4038
})
4139
}
4240

43-
44-
vm.loadingStateLiveData.observe(this, Observer {
45-
footerAdapter.submitList(it)
46-
})
47-
vm.userLiveData.observe(this, Observer {
48-
userAdapter.submitList(it)
49-
})
50-
vm.loadNextPage()
41+
viewModel.loadingStateLiveData.observe(this, Observer(footerAdapter::submitList))
42+
viewModel.userLiveData.observe(this, Observer(userAdapter::submitList))
5143
}
5244

53-
private fun onRetry() = vm.retryNextPage()
45+
private fun onRetry() = viewModel.retryNextPage()
5446

5547
private companion object {
5648
private const val VISIBLE_THRESHOLD = 3

app/src/main/java/com/hoc/mergeadapter_sample/MainVM.kt

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,105 @@ package com.hoc.mergeadapter_sample
22

33
import androidx.annotation.MainThread
44
import androidx.lifecycle.*
5+
import com.hoc.mergeadapter_sample.PlaceholderState.*
56
import kotlinx.coroutines.launch
7+
import kotlin.LazyThreadSafetyMode.NONE
68

9+
sealed class PlaceholderState {
10+
object Idle : PlaceholderState()
11+
object Loading : PlaceholderState()
12+
data class Failure(val throwable: Throwable) : PlaceholderState()
13+
}
714

8-
class MainVM : ViewModel() {
9-
private val usersD = MutableLiveData<List<User>>().apply { value = emptyList() }
10-
val userLiveData: LiveData<List<User>> get() = usersD
15+
class MainVM(private val getUsers: suspend (start: Int, limit: Int) -> List<User>) : ViewModel() {
1116

12-
private val loadingStateD =
13-
MutableLiveData<PlaceholderState>().apply { value = PlaceholderState.Idle }
14-
val loadingStateLiveData: LiveData<List<LoadingState>>
15-
get() = loadingStateD.map {
16-
when (it) {
17-
null -> emptyList()
18-
PlaceholderState.Idle -> emptyList()
19-
PlaceholderState.Loading -> listOf(LoadingState.Loading)
20-
is PlaceholderState.Error -> listOf(LoadingState.Error(it.throwable))
21-
}
17+
//region Private
18+
private val usersD by lazy(NONE) {
19+
MutableLiveData<List<User>>()
20+
.apply { value = emptyList() }
21+
.also { loadNextPage() /* load first page when first accessing*/ }
22+
}
23+
private val loadingStateD = MutableLiveData<PlaceholderState>().apply { value = Idle }
24+
private val firstPageStateD = MutableLiveData<PlaceholderState>().apply { value = Idle }
25+
26+
private var isFirstPage = true
27+
28+
private val shouldLoadNextPage: Boolean
29+
get() = if (isFirstPage) {
30+
firstPageStateD.value!! == Idle
31+
} else {
32+
loadingStateD.value!! == Idle
2233
}
2334

35+
private val shouldRetryNextPage: Boolean
36+
get() = if (isFirstPage) {
37+
firstPageStateD.value!! is Failure
38+
} else {
39+
loadingStateD.value!! is Failure
40+
}
41+
42+
//endregion
43+
44+
//region Public LiveDatas
45+
46+
val userLiveData: LiveData<List<User>> get() = usersD
47+
48+
val firstPageStateLiveData: LiveData<PlaceholderState> = firstPageStateD
49+
50+
val loadingStateLiveData: LiveData<List<PlaceholderState>>
51+
get() = loadingStateD.map { if (it == Idle) emptyList() else listOf(it) }
52+
53+
//endregion
54+
55+
//region Public methods
2456
@MainThread
2557
fun loadNextPage() {
26-
val state = loadingStateD.value
27-
if (state === null || state is PlaceholderState.Idle) {
28-
_loadNextPage()
58+
if (shouldLoadNextPage) {
59+
loadNextPageInternal()
2960
}
3061
}
3162

3263
@MainThread
3364
fun retryNextPage() {
34-
if (loadingStateD.value is PlaceholderState.Error) {
35-
_loadNextPage()
65+
if (shouldRetryNextPage) {
66+
loadNextPageInternal()
3667
}
3768
}
69+
//endregion
3870

39-
@Suppress("FunctionName")
40-
private fun _loadNextPage() {
71+
private fun loadNextPageInternal() {
4172
viewModelScope.launch {
42-
loadingStateD.value = PlaceholderState.Loading
73+
if (isFirstPage) {
74+
firstPageStateD.value = Loading
75+
} else {
76+
loadingStateD.value = Loading
77+
}
4378

44-
val currentList = usersD.value ?: emptyList()
45-
kotlin.runCatching { getUsers(start = currentList.size, limit = LIMIT) }
79+
val currentList = usersD.value!!
80+
81+
runCatching { getUsers(currentList.size, LIMIT) }
4682
.fold(
4783
onSuccess = {
84+
isFirstPage = currentList.isEmpty()
4885
usersD.value = currentList + it
49-
loadingStateD.value = PlaceholderState.Idle
86+
87+
if (isFirstPage) {
88+
firstPageStateD.value = Idle
89+
} else {
90+
loadingStateD.value = Idle
91+
}
5092
},
5193
onFailure = {
52-
loadingStateD.value = PlaceholderState.Error(it)
94+
if (isFirstPage) {
95+
firstPageStateD.value = Failure(it)
96+
} else {
97+
loadingStateD.value = Failure(it)
98+
}
5399
}
54100
)
55101
}
56102
}
57103

58-
private sealed class PlaceholderState {
59-
object Idle : PlaceholderState()
60-
object Loading : PlaceholderState()
61-
data class Error(val throwable: Throwable) : PlaceholderState()
62-
}
63-
64104
private companion object {
65105
const val LIMIT = 20
66106
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.hoc.mergeadapter_sample
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.ViewModelProvider
5+
6+
val viewModelFactoryProducer: () -> ViewModelProvider.Factory = {
7+
object : ViewModelProvider.Factory {
8+
@Suppress("UNCHECKED_CAST")
9+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
10+
if (modelClass == MainVM::class.java) {
11+
return MainVM(::getUsers) as T
12+
}
13+
error("Unknown modelClass: $modelClass")
14+
}
15+
}
16+
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ buildscript {
88
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
99
}
1010
dependencies {
11-
classpath 'com.android.tools.build:gradle:3.6.1'
11+
classpath 'com.android.tools.build:gradle:4.0.0'
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1313

1414
// NOTE: Do not place your application dependencies here; they belong
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Thu Apr 02 22:05:33 ICT 2020
1+
#Tue Jun 02 01:10:09 ICT 2020
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

0 commit comments

Comments
 (0)