Skip to content

Commit 3d34f61

Browse files
authored
refactor: use ViewState to handle ViewModel properties. (#19)
use a ViewState sealed class to model all possibles states of a ViewModel fix LiveData nullability on BindingAdapters closes #9
1 parent 0b07cd9 commit 3d34f61

File tree

7 files changed

+75
-27
lines changed

7 files changed

+75
-27
lines changed

app/src/main/java/es/ffgiraldez/comicsearch/search/presentation/SearchViewModel.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package es.ffgiraldez.comicsearch.search.presentation
22

33
import android.arch.lifecycle.MutableLiveData
44
import android.arch.lifecycle.ViewModel
5-
import android.util.Log
65
import es.ffgiraldez.comicsearch.comics.ComicRepository
76
import es.ffgiraldez.comicsearch.comics.Volume
87
import es.ffgiraldez.comicsearch.platform.toFlowable
@@ -11,21 +10,29 @@ class SearchViewModel(
1110
private val repo: ComicRepository
1211
) : ViewModel() {
1312

14-
val loading: MutableLiveData<Boolean> = MutableLiveData()
1513
val query: MutableLiveData<String> = MutableLiveData()
14+
val loading: MutableLiveData<Boolean> = MutableLiveData()
1615
val results: MutableLiveData<List<Volume>> = MutableLiveData()
1716

1817
init {
19-
loading.value = false
20-
results.value = emptyList()
2118
query.toFlowable()
22-
.doOnNext { loading.postValue(true) }
23-
.doOnNext { results.postValue(emptyList()) }
24-
.switchMap { repo.findVolume(it) }
25-
.doOnNext { loading.postValue(false) }
19+
.switchMap {
20+
repo.findVolume(it)
21+
.map { SearchViewState.result(it) }
22+
.startWith(SearchViewState.loading())
23+
}.startWith(SearchViewState.idle())
2624
.subscribe {
27-
results.postValue(it)
28-
Log.d("cambio", "busqueda completa [${it.size}] volumenes encontrados")
25+
when (it) {
26+
SearchViewState.Idle -> applyState(false, emptyList())
27+
is SearchViewState.Loading -> applyState(true, emptyList())
28+
is SearchViewState.Result -> applyState(false, it.volumeList)
29+
}
2930
}
31+
32+
}
33+
34+
private fun applyState(isLoading: Boolean, volumeList: List<Volume>) {
35+
loading.postValue(isLoading)
36+
results.postValue(volumeList)
3037
}
3138
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package es.ffgiraldez.comicsearch.search.presentation
2+
3+
import es.ffgiraldez.comicsearch.comics.Volume
4+
5+
sealed class SearchViewState {
6+
7+
companion object {
8+
fun result(volumeList: List<Volume>): SearchViewState = Result(volumeList)
9+
fun idle(): SearchViewState = Idle
10+
fun loading(): SearchViewState = Loading
11+
}
12+
13+
object Idle : SearchViewState()
14+
object Loading : SearchViewState()
15+
data class Result(val volumeList: List<Volume>) : SearchViewState()
16+
17+
}

app/src/main/java/es/ffgiraldez/comicsearch/search/ui/SearchBindingAdapters.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ fun bindSuggestionClick(search: FloatingSearchView, clickConsumer: ClickConsumer
2727
* Limit scope to apply using RecyclerView as BindingAdapter
2828
*/
2929
@BindingAdapter("adapter", "on_data_change", "on_selected", requireAll = false)
30-
fun bindData(recycler: RecyclerView, searchAdapter: SearchVolumeAdapter, data: List<Volume>, consumer: OnVolumeSelectedListener) =
30+
fun bindData(recycler: RecyclerView, searchAdapter: SearchVolumeAdapter, data: List<Volume>?, consumer: OnVolumeSelectedListener) =
3131
with(recycler) {
3232
if (adapter == null) {
3333
adapter = searchAdapter
3434
searchAdapter.onVolumeSelectedListener = consumer
3535
}
36-
searchAdapter.submitList(data)
36+
data?.let { searchAdapter.submitList(data) }
3737
}
3838

3939

app/src/main/java/es/ffgiraldez/comicsearch/search/ui/SearchScreenDelegate.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,3 @@ class SearchScreenDelegate(
2020

2121
fun onSuggestionSelected(suggestion: String) =
2222
with(search) { query.value = suggestion }
23-
}

app/src/main/java/es/ffgiraldez/comicsearch/sugestion/presentation/SuggestionViewModel.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package es.ffgiraldez.comicsearch.sugestion.presentation
22

33
import android.arch.lifecycle.MutableLiveData
44
import android.arch.lifecycle.ViewModel
5-
import android.util.Log
65
import es.ffgiraldez.comicsearch.comics.ComicRepository
76
import es.ffgiraldez.comicsearch.platform.toFlowable
87
import java.util.concurrent.TimeUnit
@@ -16,17 +15,25 @@ class SuggestionViewModel(
1615
val results: MutableLiveData<List<String>> = MutableLiveData()
1716

1817
init {
19-
loading.value = false
20-
results.value = emptyList()
2118
query.toFlowable()
2219
.debounce(400, TimeUnit.MILLISECONDS)
23-
.doOnNext { loading.postValue(true) }
24-
.doOnNext { results.postValue(emptyList()) }
25-
.switchMap { repo.findSuggestion(it) }
26-
.doOnNext { loading.postValue(false) }
20+
.switchMap { query ->
21+
repo.findSuggestion(query)
22+
.map { suggestions -> SuggestionViewState.result(suggestions) }
23+
.startWith(SuggestionViewState.loading())
24+
}.startWith(SuggestionViewState.idle())
2725
.subscribe {
28-
Log.d("cambio", "$it")
29-
results.postValue(it)
26+
when (it) {
27+
SuggestionViewState.Idle -> applyState(false, emptyList())
28+
is SuggestionViewState.Loading -> applyState(true, emptyList())
29+
is SuggestionViewState.Result -> applyState(false, it.suggestions)
30+
}
3031
}
32+
33+
}
34+
35+
private fun applyState(isLoading: Boolean, suggestions: List<String>) {
36+
loading.postValue(isLoading)
37+
results.postValue(suggestions)
3138
}
3239
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package es.ffgiraldez.comicsearch.sugestion.presentation
2+
3+
sealed class SuggestionViewState {
4+
5+
companion object {
6+
fun result(suggestions: List<String>): SuggestionViewState = Result(suggestions)
7+
fun idle(): SuggestionViewState = Idle
8+
fun loading(): SuggestionViewState = Loading
9+
}
10+
11+
object Idle : SuggestionViewState()
12+
object Loading : SuggestionViewState()
13+
data class Result(val suggestions: List<String>) : SuggestionViewState()
14+
15+
}

app/src/main/java/es/ffgiraldez/comicsearch/sugestion/ui/SuggestionBindingAdapters.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ fun bindQueryChangeListener(search: FloatingSearchView, listener: FloatingSearch
99
search.setOnQueryChangeListener(listener)
1010

1111
@BindingAdapter("suggestions")
12-
fun bindSuggestions(search: FloatingSearchView, liveData: List<String>) =
13-
liveData.map { VolumeSearchSuggestion(it) }.let { search.swapSuggestions(it) }
12+
fun bindSuggestions(search: FloatingSearchView, liveData: List<String>?) = liveData?.let {
13+
it.map { VolumeSearchSuggestion(it) }.let { search.swapSuggestions(it) }
14+
}
1415

1516
@BindingAdapter("show_progress")
16-
fun bindLoading(search: FloatingSearchView, liveData: Boolean) = when (liveData) {
17-
true -> search.showProgress()
18-
false -> search.hideProgress()
17+
fun bindLoading(search: FloatingSearchView, liveData: Boolean?) = liveData?.let {
18+
when (it) {
19+
true -> search.showProgress()
20+
false -> search.hideProgress()
21+
}
1922
}

0 commit comments

Comments
 (0)