Skip to content

Commit 031e8eb

Browse files
Prepare UseCase4
1 parent 13b83e1 commit 031e8eb

File tree

9 files changed

+199
-0
lines changed

9 files changed

+199
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@
107107
android:name=".usecases.flow.usecase3.FlowUseCase3Activity"
108108
android:exported="true" />
109109

110+
<activity
111+
android:name=".usecases.flow.usecase4.FlowUseCase4Activity"
112+
android:exported="true" />
113+
110114
</application>
111115

112116
</manifest>

app/src/main/java/com/lukaslechner/coroutineusecasesonandroid/base/UseCase.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase9.
2626
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase1.FlowUseCase1Activity
2727
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase2.FlowUseCase2Activity
2828
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase3.FlowUseCase3Activity
29+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4.FlowUseCase4Activity
2930
import kotlinx.parcelize.Parcelize
3031

3132
@Parcelize
@@ -152,6 +153,7 @@ private val coroutinesUseCases =
152153
const val flowUseCase1Description = "#1 Flow Basics"
153154
const val flowUseCase2Description = "#2 Basic Intermediate operators"
154155
const val flowUseCase3Description = "#3 Exception Handling"
156+
const val flowUseCase4Description = "#4 Exposing Flows in the ViewModel"
155157

156158
private val flowUseCases =
157159
UseCaseCategory(
@@ -166,6 +168,9 @@ private val flowUseCases =
166168
),UseCase(
167169
flowUseCase3Description,
168170
FlowUseCase3Activity::class.java
171+
),UseCase(
172+
flowUseCase4Description,
173+
FlowUseCase4Activity::class.java
169174
)
170175
)
171176
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import android.os.Bundle
4+
import androidx.activity.viewModels
5+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseActivity
6+
import com.lukaslechner.coroutineusecasesonandroid.base.flowUseCase4Description
7+
import com.lukaslechner.coroutineusecasesonandroid.databinding.ActivityFlowUsecase1Binding
8+
import com.lukaslechner.coroutineusecasesonandroid.utils.setGone
9+
import com.lukaslechner.coroutineusecasesonandroid.utils.setVisible
10+
import com.lukaslechner.coroutineusecasesonandroid.utils.toast
11+
import org.joda.time.LocalDateTime
12+
import org.joda.time.format.DateTimeFormat
13+
14+
class FlowUseCase4Activity : BaseActivity() {
15+
16+
private val binding by lazy { ActivityFlowUsecase1Binding.inflate(layoutInflater) }
17+
private val adapter = StockAdapter()
18+
19+
private val viewModel: FlowUseCase4ViewModel by viewModels {
20+
ViewModelFactory(NetworkStockPriceDataSource(mockApi(applicationContext)))
21+
}
22+
23+
override fun onCreate(savedInstanceState: Bundle?) {
24+
super.onCreate(savedInstanceState)
25+
setContentView(binding.root)
26+
binding.recyclerView.adapter = adapter
27+
28+
viewModel.currentStockPriceAsLiveData.observe(this) { uiState ->
29+
if (uiState != null) {
30+
render(uiState)
31+
}
32+
}
33+
}
34+
35+
private fun render(uiState: UiState) {
36+
when (uiState) {
37+
is UiState.Loading -> {
38+
binding.progressBar.setVisible()
39+
binding.recyclerView.setGone()
40+
}
41+
is UiState.Success -> {
42+
binding.recyclerView.setVisible()
43+
binding.lastUpdateTime.text =
44+
"lastUpdateTime: ${LocalDateTime.now().toString(DateTimeFormat.fullTime())}"
45+
adapter.stockList = uiState.stockList
46+
binding.progressBar.setGone()
47+
}
48+
is UiState.Error -> {
49+
toast(uiState.message)
50+
binding.progressBar.setGone()
51+
}
52+
}
53+
}
54+
55+
override fun getToolbarTitle() = flowUseCase4Description
56+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.asLiveData
5+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseViewModel
6+
import kotlinx.coroutines.flow.map
7+
import kotlinx.coroutines.flow.onCompletion
8+
import kotlinx.coroutines.flow.onStart
9+
import timber.log.Timber
10+
11+
class FlowUseCase4ViewModel(
12+
stockPriceDataSource: StockPriceDataSource
13+
) : BaseViewModel<UiState>() {
14+
15+
val currentStockPriceAsLiveData: LiveData<UiState> = stockPriceDataSource
16+
.latestStockList
17+
.map { stockList ->
18+
UiState.Success(stockList) as UiState
19+
}
20+
.onStart {
21+
emit(UiState.Loading)
22+
}
23+
.onCompletion {
24+
Timber.tag("Flow").d("Flow has completed.")
25+
}
26+
.asLiveData()
27+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import android.content.Context
4+
import com.google.gson.Gson
5+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.createFlowMockApi
6+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.fakeCurrentStockPrices
7+
import com.lukaslechner.coroutineusecasesonandroid.utils.MockNetworkInterceptor
8+
9+
fun mockApi(context: Context) =
10+
createFlowMockApi(
11+
MockNetworkInterceptor()
12+
.mock(
13+
path = "/current-stock-prices",
14+
body = { Gson().toJson(fakeCurrentStockPrices(context)) },
15+
status = 200,
16+
delayInMs = 1500,
17+
)
18+
)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import android.view.LayoutInflater
4+
import android.view.View
5+
import android.view.ViewGroup
6+
import androidx.recyclerview.widget.RecyclerView
7+
import com.lukaslechner.coroutineusecasesonandroid.R
8+
import com.lukaslechner.coroutineusecasesonandroid.databinding.RecyclerviewItemStockBinding
9+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.Stock
10+
import java.text.NumberFormat
11+
12+
class StockAdapter: RecyclerView.Adapter<StockAdapter.ViewHolder>() {
13+
14+
var stockList: List<Stock>? = null
15+
set(value) {
16+
field = value
17+
notifyDataSetChanged()
18+
}
19+
20+
private val formatter: NumberFormat = NumberFormat.getCurrencyInstance()
21+
22+
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
23+
val binding = RecyclerviewItemStockBinding.bind(view)
24+
}
25+
26+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
27+
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item_stock, parent, false)
28+
return ViewHolder(view)
29+
}
30+
31+
override fun onBindViewHolder(holder: ViewHolder, position: Int) = with(holder.binding){
32+
val stock = stockList?.get(position) ?: return@with
33+
rank.text = stock.rank.toString()
34+
name.text = stock.name
35+
val currentPriceFormatted: String = formatter.format(stock.currentPrice)
36+
currentPrice.text = currentPriceFormatted
37+
}
38+
39+
override fun getItemCount(): Int {
40+
return stockList?.size ?: 0
41+
}
42+
43+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.FlowMockApi
4+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.Stock
5+
import kotlinx.coroutines.delay
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.flow
8+
import timber.log.Timber
9+
10+
interface StockPriceDataSource {
11+
val latestStockList: Flow<List<Stock>>
12+
}
13+
14+
class NetworkStockPriceDataSource(mockApi: FlowMockApi) : StockPriceDataSource {
15+
16+
override val latestStockList: Flow<List<Stock>> = flow {
17+
while (true) {
18+
Timber.tag("Flow").d("Fetching current stock prices")
19+
val currentStockList = mockApi.getCurrentStockPrices()
20+
emit(currentStockList)
21+
delay(5_000)
22+
}
23+
}
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.mock.Stock
4+
5+
sealed class UiState {
6+
object Loading : UiState()
7+
data class Success(val stockList: List<Stock>) : UiState()
8+
data class Error(val message: String) : UiState()
9+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase4
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.ViewModelProvider
5+
6+
class ViewModelFactory(private val stockPriceDataSource: StockPriceDataSource) :
7+
ViewModelProvider.Factory {
8+
9+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
10+
return modelClass.getConstructor(StockPriceDataSource::class.java)
11+
.newInstance(stockPriceDataSource)
12+
}
13+
}

0 commit comments

Comments
 (0)