Skip to content

Commit 24ca73f

Browse files
committed
[ Kotlin+MVVM ] Migrate ViewModel class from Java to Kotlin
1 parent 72f9bba commit 24ca73f

File tree

5 files changed

+165
-205
lines changed

5 files changed

+165
-205
lines changed

app/src/main/java/me/li2/android/architecture/ui/articles/view/ArticlesFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class ArticlesFragment : DaggerFragment() {
4949

5050
mViewModel.uiModel.observe(this, Observer { uiModel -> updateView(uiModel) })
5151

52-
mViewModel.loadingIndicatorVisibility.observe(this, Observer { visible -> setLoadingIndicatorVisibility(visible!!) }) // TODO why Found String?
52+
mViewModel.loadingIndicator.observe(this, Observer { visible -> setLoadingIndicatorVisibility(visible!!) }) // TODO why Found String?
5353

5454
mViewModel.snackbarMessage.observe(this, Observer { message -> showSnackBar(message!!) }) // TODO why Found String?
5555
}

app/src/main/java/me/li2/android/architecture/ui/articles/viewmodel/ArticlesViewModel.java

Lines changed: 0 additions & 161 deletions
This file was deleted.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package me.li2.android.architecture.ui.articles.viewmodel
2+
3+
import android.arch.lifecycle.LiveData
4+
import android.arch.lifecycle.MutableLiveData
5+
import android.arch.lifecycle.Transformations
6+
import android.arch.lifecycle.ViewModel
7+
import android.util.Log
8+
import android.view.View
9+
import arch.NoNetworkException
10+
import arch.Resource
11+
import arch.Status
12+
import io.reactivex.Completable
13+
import io.reactivex.functions.Action
14+
import io.reactivex.functions.Consumer
15+
import me.li2.android.architecture.R
16+
import me.li2.android.architecture.data.model.Article
17+
import me.li2.android.architecture.data.repository.ArticlesRepository
18+
import me.li2.android.architecture.ui.articles.view.ArticlesNavigator
19+
import me.li2.android.architecture.utils.BaseResourceProvider
20+
import java.util.*
21+
22+
/**
23+
* ViewModel to expose states for the list of articles view, and handle all user actions.
24+
*
25+
* - Ask data from repository [ArticlesViewModel.mRepository], and then
26+
* construct the source data to view data which contains all UI state.
27+
*
28+
* - Expose streams for view to subscribe the UI state,
29+
* such as [ArticlesViewModel.getUiModel], [ArticlesViewModel.getLoadingIndicatorVisibility]
30+
*
31+
* - Expose public methods for view to handle user actions,
32+
* such as force pull-refresh [ArticlesViewModel.forceUpdateArticles]
33+
*
34+
* - For user action in the sub-view, such as click or check in the list view item,
35+
* implement [Action] or [Consumer] in the ViewModel
36+
* [ArticlesViewModel.handleArticleTaped], and then
37+
* pass it as parameter to ViewHolder constructor [ArticleItem.ArticleItem].
38+
*
39+
* @author Weiyi Li on 13/7/18 | https://github.com/li2
40+
*/
41+
42+
private const val TAG = "ArticlesViewModel"
43+
44+
class ArticlesViewModel(private val mRepository: ArticlesRepository,
45+
private val mResourceProvider: BaseResourceProvider,
46+
private val mNavigator: ArticlesNavigator
47+
) : ViewModel() {
48+
49+
/**
50+
* Live stream emits true if the progress indicator should be displayed, false otherwise.
51+
*/
52+
val loadingIndicator = MutableLiveData<Boolean>()
53+
54+
/**
55+
* Live stream emits the string which should be displayed in the snackbar.
56+
*/
57+
val snackbarMessage = MutableLiveData<String>()
58+
59+
/**
60+
* Live stream emits the [ArticlesUiModel] which is the model for the articles list screen.
61+
*/
62+
val uiModel: LiveData<ArticlesUiModel> =
63+
Transformations.map(getArticleItems()) { resource ->
64+
loadingIndicator.value = resource.status == Status.LOADING
65+
66+
if (resource.status == Status.ERROR) {
67+
if (resource.throwable is NoNetworkException) {
68+
snackbarMessage.value = mResourceProvider.getString(R.string.status_no_connect)
69+
} else {
70+
Log.d(TAG, "Failed to get UI model : ${resource.errorMessage}")
71+
snackbarMessage.value = resource.errorMessage
72+
}
73+
}
74+
75+
if (resource.data != null) {
76+
constructArticlesUiModel(resource.data)
77+
} else {
78+
null
79+
}
80+
}
81+
82+
/**
83+
* Convert [Article] (raw data model) to [ArticleItem] (view data model)
84+
* @return
85+
*/
86+
private fun getArticleItems(): LiveData<Resource<List<ArticleItem>>> =
87+
Transformations.map(mRepository.loadArticles()) { resource ->
88+
var articleItems: MutableList<ArticleItem>? = null
89+
if (resource.data != null) {
90+
articleItems = ArrayList()
91+
for (article in resource.data) {
92+
articleItems.add(constructArticleItem(article))
93+
}
94+
}
95+
Resource<List<ArticleItem>>(resource.status, articleItems, resource.errorMessage, resource.code, resource.throwable)
96+
}
97+
98+
private fun constructArticlesUiModel(articleItems: List<ArticleItem>): ArticlesUiModel {
99+
val isArticlesListVisible = !articleItems.isEmpty()
100+
val isNoArticlesViewVisible = !isArticlesListVisible
101+
102+
return ArticlesUiModel(
103+
isArticlesListVisible,
104+
articleItems,
105+
isNoArticlesViewVisible,
106+
mResourceProvider.getString(R.string.no_articles_all)
107+
)
108+
}
109+
110+
/** Convert raw data from server to view data which contains all the UI state.
111+
because UI doesn't care too much details about the raw data. notebyweiyi */
112+
private fun constructArticleItem(article: Article): ArticleItem {
113+
return ArticleItem(
114+
article.title,
115+
article.description,
116+
article.imageHref,
117+
Consumer { view -> handleArticleTaped(article, view) }
118+
)
119+
}
120+
121+
private fun handleArticleTaped(article: Article, sharedElement: View) {
122+
mNavigator.openArticleDetails(article.id, sharedElement)
123+
}
124+
125+
/**
126+
* Trigger a force update of the articles.
127+
*/
128+
fun forceUpdateArticles(): Completable? {
129+
loadingIndicator.value = true
130+
// TODO
131+
return null
132+
}
133+
}

app/src/main/java/me/li2/android/architecture/ui/articles/viewmodel/ArticlesViewModelFactory.java

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package me.li2.android.architecture.ui.articles.viewmodel
2+
3+
import android.arch.lifecycle.ViewModel
4+
import android.arch.lifecycle.ViewModelProvider
5+
import me.li2.android.architecture.data.repository.ArticlesRepository
6+
import me.li2.android.architecture.ui.articles.view.ArticlesNavigator
7+
import me.li2.android.architecture.utils.BaseResourceProvider
8+
import javax.inject.Inject
9+
10+
/**
11+
* Created by weiyi on 24/7/18.
12+
* https://github.com/li2
13+
*
14+
* TODO why cannot inject directly in the ViewModel
15+
*/
16+
class ArticlesViewModelFactory @Inject
17+
constructor() : ViewModelProvider.NewInstanceFactory() {
18+
19+
@Inject
20+
lateinit var mRepository: ArticlesRepository
21+
22+
@Inject
23+
lateinit var mNavigator: ArticlesNavigator
24+
25+
@Inject
26+
lateinit var mResourceProvider: BaseResourceProvider
27+
28+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
29+
return ArticlesViewModel(mRepository, mResourceProvider, mNavigator) as T
30+
}
31+
}

0 commit comments

Comments
 (0)