Skip to content

Commit 79fb174

Browse files
Prepare Flow UseCase2 for the exercise
1 parent 18d0e67 commit 79fb174

File tree

9 files changed

+210
-0
lines changed

9 files changed

+210
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
android:name=".usecases.flow.usecase1.FlowUseCase1Activity"
100100
android:exported="true" />
101101

102+
<activity
103+
android:name=".usecases.flow.usecase2.FlowUseCase2Activity"
104+
android:exported="true" />
105+
102106
</application>
103107

104108
</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
@@ -24,6 +24,7 @@ import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase7.
2424
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase8.RoomAndCoroutinesActivity
2525
import com.lukaslechner.coroutineusecasesonandroid.usecases.coroutines.usecase9.DebuggingCoroutinesActivity
2626
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase1.FlowUseCase1Activity
27+
import com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase2.FlowUseCase2Activity
2728
import kotlinx.parcelize.Parcelize
2829

2930
@Parcelize
@@ -148,6 +149,7 @@ private val coroutinesUseCases =
148149
)
149150

150151
const val flowUseCase1Description = "#1 Flow Basics"
152+
const val flowUseCase2Description = "#2 Basic Intermediate operators"
151153

152154
private val flowUseCases =
153155
UseCaseCategory(
@@ -156,6 +158,9 @@ private val flowUseCases =
156158
UseCase(
157159
flowUseCase1Description,
158160
FlowUseCase1Activity::class.java
161+
),UseCase(
162+
flowUseCase2Description,
163+
FlowUseCase2Activity::class.java
159164
)
160165
)
161166
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase2
2+
3+
import android.os.Bundle
4+
import androidx.activity.viewModels
5+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseActivity
6+
import com.lukaslechner.coroutineusecasesonandroid.base.flowUseCase2Description
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 kotlinx.coroutines.Dispatchers
12+
import org.joda.time.LocalDateTime
13+
import org.joda.time.format.DateTimeFormat
14+
15+
class FlowUseCase2Activity : BaseActivity() {
16+
17+
private val binding by lazy { ActivityFlowUsecase1Binding.inflate(layoutInflater) }
18+
private val adapter = StockAdapter()
19+
20+
private val viewModel: FlowUseCase2ViewModel by viewModels {
21+
ViewModelFactory(
22+
NetworkStockPriceDataSource(mockApi(applicationContext)),
23+
Dispatchers.Default
24+
)
25+
}
26+
27+
override fun onCreate(savedInstanceState: Bundle?) {
28+
super.onCreate(savedInstanceState)
29+
setContentView(binding.root)
30+
binding.recyclerView.adapter = adapter
31+
32+
viewModel.currentStockPriceAsLiveData.observe(this) { uiState ->
33+
if (uiState != null) {
34+
render(uiState)
35+
}
36+
}
37+
}
38+
39+
private fun render(uiState: UiState) {
40+
when (uiState) {
41+
is UiState.Loading -> {
42+
binding.progressBar.setVisible()
43+
binding.recyclerView.setGone()
44+
}
45+
is UiState.Success -> {
46+
binding.recyclerView.setVisible()
47+
binding.lastUpdateTime.text =
48+
"lastUpdateTime: ${LocalDateTime.now().toString(DateTimeFormat.fullTime())}"
49+
adapter.stockList = uiState.stockList
50+
binding.progressBar.setGone()
51+
}
52+
is UiState.Error -> {
53+
toast(uiState.message)
54+
binding.progressBar.setGone()
55+
}
56+
}
57+
}
58+
59+
override fun getToolbarTitle() = flowUseCase2Description
60+
}
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.usecase2
2+
3+
import androidx.lifecycle.LiveData
4+
import com.lukaslechner.coroutineusecasesonandroid.base.BaseViewModel
5+
import kotlinx.coroutines.CoroutineDispatcher
6+
7+
class FlowUseCase2ViewModel(
8+
stockPriceDataSource: StockPriceDataSource,
9+
defaultDispatcher: CoroutineDispatcher
10+
) : BaseViewModel<UiState>() {
11+
12+
/*
13+
14+
Flow exercise 1 Goals
15+
1) only update stock list when Alphabet(Google) (stock.name ="Alphabet (Google)") stock price is > 2300$
16+
2) only show stocks of "United States" (stock.country == "United States")
17+
3) show the correct rank (stock.rank) within "United States", not the world wide rank
18+
4) filter out Apple (stock.name ="Apple") and Microsoft (stock.name ="Microsoft"), so that Google is number one
19+
5) only show company if it is one of the biggest 10 companies of the "United States" (stock.rank <= 10)
20+
6) stop flow collection after 10 emissions from the dataSource
21+
7) log out the number of the current emission so that we can check if flow collection stops after exactly 10 emissions
22+
8) Perform all flow processing on a background thread
23+
24+
*/
25+
26+
val currentStockPriceAsLiveData: LiveData<UiState> = TODO()
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.usecase2
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.usecase2
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.usecase2
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.usecase2
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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.lukaslechner.coroutineusecasesonandroid.usecases.flow.usecase2
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.ViewModelProvider
5+
import kotlinx.coroutines.CoroutineDispatcher
6+
7+
class ViewModelFactory(
8+
private val stockPriceDataSource: StockPriceDataSource,
9+
private val defaultDispatcher: CoroutineDispatcher
10+
) :
11+
ViewModelProvider.Factory {
12+
13+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
14+
return modelClass.getConstructor(
15+
StockPriceDataSource::class.java,
16+
CoroutineDispatcher::class.java
17+
)
18+
.newInstance(stockPriceDataSource, defaultDispatcher)
19+
}
20+
}

0 commit comments

Comments
 (0)