Skip to content

Commit d6c8534

Browse files
replace livedata with coroutine flows and channels
1 parent e00a510 commit d6c8534

25 files changed

+293
-310
lines changed

README.md

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,53 @@
44

55
### API Key
66

7-
To run the application you need to supply an API key from [TMBD](https://developers.themoviedb.org/3/getting-started/introduction). When you get the key please add following variable to your local environment:
7+
To run the application you need to supply an API key
8+
from [TMBD](https://developers.themoviedb.org/3/getting-started/introduction). When you get the key
9+
please add following variable to your local environment:
810

911
`` API_KEY_TMDB = Your API Key ``
1012

11-
How to set an environment variable in [Mac](https://medium.com/@himanshuagarwal1395/setting-up-environment-variables-in-macos-sierra-f5978369b255) / [Windows](https://www.architectryan.com/2018/08/31/how-to-change-environment-variables-on-windows-10/)
13+
How to set an environment variable
14+
in [Mac](https://medium.com/@himanshuagarwal1395/setting-up-environment-variables-in-macos-sierra-f5978369b255) / [Windows](https://www.architectryan.com/2018/08/31/how-to-change-environment-variables-on-windows-10/)
1215

1316
### Code style [*](https://github.com/VMadalin/kotlin-sample-app)
1417

15-
To maintain the style and quality of the code, are used the bellow static analysis tools. All of them use properly configuration and you find them in the project root directory `config/.{toolName}`.
18+
To maintain the style and quality of the code, are used the bellow static analysis tools. All of
19+
them use properly configuration and you find them in the project root
20+
directory `config/.{toolName}`.
1621

17-
| Tools | Config file | Check command | Fix command |
18-
|-----------------------------------|---------------------------------------:|------------------------------|---------------------------|
19-
| [detekt][detekt] | [.detekt.yml](/config/.detekt.yml) | `./gradlew detekt` | - |
20-
| [ktlint][ktlint] | - | `./gradlew ktlint` | `./gradlew ktlintFormat` |
21-
| [spotless][spotless] | - | `./gradlew spotlessCheck` | `./gradlew spotlessApply` |
22-
| [lint][lint] | [.lint.xml](/config/.lint.xml) | `./gradlew lint` | - |
23-
| [gradle versions plugin][gvPlugin]| - | `./gradlew dependencyUpdates`| - |
22+
| Tools | Config file | Check command | Fix command |
23+
|------------------------------------|-----------------------------------:|-------------------------------|---------------------------|
24+
| [detekt][detekt] | [.detekt.yml](/config/.detekt.yml) | `./gradlew detekt` | - |
25+
| [ktlint][ktlint] | - | `./gradlew ktlint` | `./gradlew ktlintFormat` |
26+
| [spotless][spotless] | - | `./gradlew spotlessCheck` | `./gradlew spotlessApply` |
27+
| [lint][lint] | [.lint.xml](/config/.lint.xml) | `./gradlew lint` | - |
28+
| [gradle versions plugin][gvPlugin] | - | `./gradlew dependencyUpdates` | - |
2429

25-
All these tools, except [Gradle Versions Plugin][gvPlugin], are integrated in [pre-commit git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), in order
26-
ensure that all static analysis and tests passes before you can commit your changes. [Gradle Versions Plugin][gvPlugin] can be run optionally. To skip them for specific commit add this option at your git command:
30+
All these tools, except [Gradle Versions Plugin][gvPlugin], are integrated
31+
in [pre-commit git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), in order
32+
ensure that all static analysis and tests passes before you can commit your
33+
changes. [Gradle Versions Plugin][gvPlugin] can be run optionally. To skip them for specific commit
34+
add this option at your git command:
2735

2836
```properties
2937
git commit --no-verify
3038
```
3139

32-
It's highly recommended to fix broken code styles. There is [a gradle task](/build.gradle#L57) which execute `ktlintFormat` and `spotlessApply` for you:
40+
It's highly recommended to fix broken code styles. There is [a gradle task](/build.gradle#L57) which
41+
execute `ktlintFormat` and `spotlessApply` for you:
3342

3443
```properties
3544
./gradlew reformat
3645
```
3746

47+
The pre-commit git hooks have exactly the same checks as [CircleCI](https://circleci.com/) and are
48+
defined in this [script](/config/scripts/git-hooks/pre-commit.sh). This step ensures that all
49+
commits comply with the established rules. However the continuous integration will ultimately be
50+
validated that the changes are correct.
3851

39-
The pre-commit git hooks have exactly the same checks as [CircleCI](https://circleci.com/) and are defined in this [script](/config/scripts/git-hooks/pre-commit.sh). This step ensures that all commits comply with the established rules. However the continuous integration will ultimately be validated that the changes are correct.
40-
41-
42-
If you want to know more about naming convention, code style and more please look at our [Android guideline](https://github.com/adessoTurkey/android-guideline) repository.
52+
If you want to know more about naming convention, code style and more please look at
53+
our [Android guideline](https://github.com/adessoTurkey/android-guideline) repository.
4354

4455
## Architecture
4556

@@ -50,7 +61,8 @@ If you want to know more about naming convention, code style and more please loo
5061

5162
**ViewModel:** Can have simple UI logic but most of the time just gets the data from UseCase
5263

53-
**UseCase:** Contains all business rules and they written in the manner of single responsibility principle
64+
**UseCase:** Contains all business rules and they written in the manner of single responsibility
65+
principle
5466

5567
**Repository:** Single source of data. Responsible to get data from one or more data sources
5668

@@ -60,17 +72,21 @@ If you want to know more about naming convention, code style and more please loo
6072

6173
#### Dependencies
6274

63-
- **[Navigation Component](https://developer.android.com/jetpack/androidx/releases/navigation):** Consistent navigation between views
64-
- **[LiveData](https://developer.android.com/topic/libraries/architecture/livedata):** Lifecycle aware observable and data holder
65-
- **[ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel):** Holds UI data across configuration changes
66-
- **[Databinding](https://developer.android.com/topic/libraries/data-binding/):** Binds UI components in layouts to data sources
75+
- **[Navigation Component](https://developer.android.com/jetpack/androidx/releases/navigation):**
76+
Consistent navigation between views
77+
- **[Flow](https://developer.android.com/kotlin/flow):** Asynchronous data streams
78+
- **[ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel):** Holds UI
79+
data across configuration changes
80+
- **[Databinding](https://developer.android.com/topic/libraries/data-binding/):** Binds UI
81+
components in layouts to data sources
6782
- **[Dagger](https://github.com/google/dagger):** Dependency injector
6883
- **[Coroutines](https://github.com/Kotlin/kotlinx.coroutines):** Asynchronous programming
6984
- **[Glide](https://github.com/bumptech/glide):** Image loading and caching
7085
- **[Lottie](https://github.com/airbnb/lottie-android):** JSON based animations
7186
- **[Retrofit](https://github.com/square/retrofit):** Type safe HTTP client
7287
- **[Moshi](https://github.com/square/moshi):** JSON serializer/deserializer
73-
- **[Room](https://developer.android.com/topic/libraries/architecture/room):** Object mapping for SQLite
88+
- **[Room](https://developer.android.com/topic/libraries/architecture/room):** Object mapping for
89+
SQLite
7490

7591
#### Plugins
7692

@@ -99,7 +115,11 @@ limitations under the License.
99115
```
100116

101117
[detekt]: https://github.com/arturbosch/detekt
118+
102119
[ktlint]: https://github.com/pinterest/ktlint
103-
[spotless]: https://github.com/diffplug/spotless
120+
121+
[spotless]: https://github.com/diffplug/spotless
122+
104123
[lint]: https://developer.android.com/studio/write/lint
124+
105125
[gvPlugin]: https://github.com/ben-manes/gradle-versions-plugin

app/src/main/kotlin/com/adesso/movee/base/BaseAndroidViewModel.kt

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,32 @@ import android.annotation.SuppressLint
44
import android.app.Application
55
import androidx.annotation.StringRes
66
import androidx.lifecycle.AndroidViewModel
7-
import androidx.lifecycle.LiveData
8-
import androidx.lifecycle.MutableLiveData
97
import androidx.navigation.NavDirections
108
import com.adesso.movee.R
119
import com.adesso.movee.internal.popup.PopUpType
1210
import com.adesso.movee.internal.popup.PopupCallback
1311
import com.adesso.movee.internal.popup.PopupUiModel
14-
import com.adesso.movee.internal.util.Event
1512
import com.adesso.movee.internal.util.Failure
1613
import com.adesso.movee.navigation.NavigationCommand
1714
import kotlinx.coroutines.CoroutineScope
1815
import kotlinx.coroutines.Dispatchers
1916
import kotlinx.coroutines.SupervisorJob
17+
import kotlinx.coroutines.channels.Channel
18+
import kotlinx.coroutines.flow.receiveAsFlow
2019
import kotlinx.coroutines.withContext
2120

2221
@Suppress("ConvertSecondaryConstructorToPrimary")
2322
@SuppressLint("StaticFieldLeak")
2423
abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel(application) {
2524

26-
private val _failurePopup = MutableLiveData<Event<PopupUiModel>>()
27-
val failurePopup: LiveData<Event<PopupUiModel>> = _failurePopup
25+
private val _failurePopup = Channel<PopupUiModel>(Channel.CONFLATED)
26+
val failurePopup = _failurePopup.receiveAsFlow()
2827

29-
private val _success = MutableLiveData<Event<String>>()
30-
val success: LiveData<Event<String>> = _success
28+
private val _success = Channel<String>(Channel.CONFLATED)
29+
val success = _success.receiveAsFlow()
3130

32-
private val _navigation = MutableLiveData<Event<NavigationCommand>>()
33-
val navigation: LiveData<Event<NavigationCommand>> = _navigation
31+
private val _navigation = Channel<NavigationCommand>(Channel.CONFLATED)
32+
val navigation = _navigation.receiveAsFlow()
3433

3534
private val viewModelJob = SupervisorJob()
3635

@@ -43,31 +42,36 @@ abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel
4342
"",
4443
getString(R.string.common_error_network_connection)
4544
)
45+
4646
is Failure.UnknownHostError -> Pair("", getString(R.string.common_error_unknown_host))
4747
is Failure.ServerError -> Pair("", failure.message)
4848
is Failure.JsonError, is Failure.EmptyResponse -> Pair(
4949
"",
5050
getString(R.string.common_error_invalid_response)
5151
)
52+
5253
is Failure.FormValidationError -> Pair(
5354
getString(R.string.common_title_popup_form_validation),
5455
failure.message
5556
?: getString(R.string.common_error_invalid_form)
5657
)
58+
5759
is Failure.IoError -> Pair("", getString(R.string.common_error_can_not_save_data))
5860
is Failure.UnknownError -> Pair(
5961
"",
6062
failure.exception.localizedMessage ?: getString(R.string.common_error_unknown)
6163
)
64+
6265
is Failure.HttpError -> Pair(
6366
"",
6467
getString(R.string.common_error_http, failure.code.toString())
6568
)
69+
6670
is Failure.TimeOutError -> Pair("", getString(R.string.common_error_timeout))
6771
else -> Pair("", failure.message ?: failure.toString())
6872
}
6973

70-
_failurePopup.value = Event(
74+
_failurePopup.trySend(
7175
PopupUiModel(
7276
title = title,
7377
message = message,
@@ -77,27 +81,27 @@ abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel
7781
}
7882

7983
protected fun showSnackBar(message: String) {
80-
_success.value = Event(message)
84+
_success.trySend(message)
8185
}
8286

8387
fun navigate(directions: NavDirections) {
84-
_navigation.value = Event(NavigationCommand.ToDirection(directions))
88+
_navigation.trySend(NavigationCommand.ToDirection(directions))
8589
}
8690

8791
fun navigate(deepLink: String) {
88-
_navigation.value = Event(NavigationCommand.ToDeepLink(deepLink))
92+
_navigation.trySend(NavigationCommand.ToDeepLink(deepLink))
8993
}
9094

9195
fun navigate(@StringRes deepLinkRes: Int) {
9296
navigate(getString(deepLinkRes))
9397
}
9498

9599
fun navigate(model: PopupUiModel, callback: PopupCallback?) {
96-
_navigation.value = Event(NavigationCommand.Popup(model, callback))
100+
_navigation.trySend(NavigationCommand.Popup(model, callback))
97101
}
98102

99103
fun navigateBack() {
100-
_navigation.value = Event(NavigationCommand.Back)
104+
_navigation.trySend(NavigationCommand.Back)
101105
}
102106

103107
protected suspend fun onUIThread(block: suspend CoroutineScope.() -> Unit) {

app/src/main/kotlin/com/adesso/movee/base/BaseBottomSheetDialogFragment.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import androidx.navigation.fragment.FragmentNavigatorExtras
1919
import androidx.navigation.fragment.findNavController
2020
import androidx.navigation.navGraphViewModels
2121
import com.adesso.movee.BR
22-
import com.adesso.movee.internal.extension.observeNonNull
22+
import com.adesso.movee.internal.extension.collectFlow
2323
import com.adesso.movee.internal.extension.showPopup
2424
import com.adesso.movee.internal.util.functional.lazyThreadSafetyNone
2525
import com.adesso.movee.navigation.NavigationCommand
@@ -103,10 +103,9 @@ abstract class BaseBottomSheetDialogFragment<VM : BaseAndroidViewModel, B : View
103103
}
104104

105105
private fun observeNavigation() {
106-
viewModel.navigation.observeNonNull(viewLifecycleOwner) {
107-
it.getContentIfNotHandled()?.let { command ->
108-
handleNavigation(command)
109-
}
106+
107+
collectFlow(viewModel.navigation) { command ->
108+
handleNavigation(command)
110109
}
111110
}
112111

@@ -115,33 +114,36 @@ abstract class BaseBottomSheetDialogFragment<VM : BaseAndroidViewModel, B : View
115114
is NavigationCommand.ToDirection -> {
116115
findNavController().navigate(command.directions, getExtras())
117116
}
117+
118118
is NavigationCommand.ToDeepLink -> {
119119
(activity as? MainActivity)
120120
?.navController
121121
?.navigate(command.deepLink.toUri(), null, getExtras())
122122
}
123+
123124
is NavigationCommand.Popup -> {
124125
with(command) {
125126
context?.showPopup(model, callback)
126127
}
127128
}
129+
128130
is NavigationCommand.Back -> findNavController().navigateUp()
129131
}
130132
}
131133

132134
private fun observeFailure() {
133-
viewModel.failurePopup.observeNonNull(viewLifecycleOwner) {
134-
it.getContentIfNotHandled()?.let { popupUiModel ->
135-
context?.showPopup(popupUiModel)
136-
}
135+
136+
collectFlow(viewModel.failurePopup) { popupUiModel ->
137+
138+
context?.showPopup(popupUiModel)
137139
}
138140
}
139141

140142
private fun observeSuccess() {
141-
viewModel.success.observeNonNull(viewLifecycleOwner) {
142-
it.getContentIfNotHandled()?.let { message ->
143-
showSnackBarMessage(message)
144-
}
143+
144+
collectFlow(viewModel.success) { message ->
145+
146+
showSnackBarMessage(message)
145147
}
146148
}
147149

app/src/main/kotlin/com/adesso/movee/base/BaseFragment.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import androidx.navigation.fragment.FragmentNavigatorExtras
1818
import androidx.navigation.fragment.findNavController
1919
import androidx.navigation.navGraphViewModels
2020
import com.adesso.movee.BR
21-
import com.adesso.movee.internal.extension.observeNonNull
21+
import com.adesso.movee.internal.extension.collectFlow
22+
import com.adesso.movee.internal.extension.collectFlowNonNull
2223
import com.adesso.movee.internal.extension.showPopup
2324
import com.adesso.movee.internal.util.functional.lazyThreadSafetyNone
2425
import com.adesso.movee.navigation.NavigationCommand
@@ -88,10 +89,10 @@ abstract class BaseFragment<VM : BaseAndroidViewModel, B : ViewDataBinding> :
8889
}
8990

9091
private fun observeNavigation() {
91-
viewModel.navigation.observeNonNull(viewLifecycleOwner) {
92-
it.getContentIfNotHandled()?.let { command ->
93-
handleNavigation(command)
94-
}
92+
93+
collectFlow(viewModel.navigation) { command ->
94+
95+
handleNavigation(command)
9596
}
9697
}
9798

@@ -100,33 +101,36 @@ abstract class BaseFragment<VM : BaseAndroidViewModel, B : ViewDataBinding> :
100101
is NavigationCommand.ToDirection -> {
101102
findNavController().navigate(command.directions, getExtras())
102103
}
104+
103105
is NavigationCommand.ToDeepLink -> {
104106
(activity as? MainActivity)
105107
?.navController
106108
?.navigate(command.deepLink.toUri(), null, getExtras())
107109
}
110+
108111
is NavigationCommand.Popup -> {
109112
with(command) {
110113
context?.showPopup(model, callback)
111114
}
112115
}
116+
113117
is NavigationCommand.Back -> findNavController().navigateUp()
114118
}
115119
}
116120

117121
private fun observeFailure() {
118-
viewModel.failurePopup.observeNonNull(viewLifecycleOwner) {
119-
it.getContentIfNotHandled()?.let { popupUiModel ->
120-
context?.showPopup(popupUiModel)
121-
}
122+
123+
collectFlow(viewModel.failurePopup) { popupUiModel ->
124+
125+
context?.showPopup(popupUiModel)
122126
}
123127
}
124128

125129
private fun observeSuccess() {
126-
viewModel.success.observeNonNull(viewLifecycleOwner) {
127-
it.getContentIfNotHandled()?.let { message ->
128-
showSnackBarMessage(message)
129-
}
130+
131+
collectFlowNonNull(viewModel.success) { message ->
132+
133+
showSnackBarMessage(message)
130134
}
131135
}
132136

0 commit comments

Comments
 (0)