summaryrefslogtreecommitdiff
path: root/DEVELOPMENT.md
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-07-28 06:04:22 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-07-28 06:04:22 +0000
commitb4d35c1c12120503e74d7ae99edd94302673acf6 (patch)
treef87e29f670323b7173e5e3875112271c8835a5d3 /DEVELOPMENT.md
parent3ca73e64ddd25c7c20eca2e4e0db77032db848c0 (diff)
#5444 Fix CPU consumption - remove flow-mvi dependency
Diffstat (limited to 'DEVELOPMENT.md')
-rw-r--r--DEVELOPMENT.md184
1 files changed, 3 insertions, 181 deletions
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 75e1535..2743aac 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -29,11 +29,9 @@ In this app, we have implemented MVI using [Kotlin Flow](https://kotlinlang.org/
<img src="art/MVI-Feature.png" width="336" height="332">
Elements of a feature:
-1. **Actor**: It is just a function that takes current state, user action as input and produces an effect (result) as output. This function generally makes the call to external APIs and usecases.
-2. **Reducer**: It is also a very simple function whose inputs are current state, effect from the actor and it returns new state.
-3. **State**: Simple POJO (kotlin data class) representing various UI states of the application.
-4. **Effect**: A POJO (kotlin data class) which is returned from the actor function.
-5. **SingleEventProducer**: This is a function which is invoked by the reducer to publish single events (that can/should only be consumed once like displaying toast, snackbar message or sending an analytics event). This function takes action, effect, current state as input and it returns a `SingleEvent`. By default this function is null for any Feature.
+1. **Action**: The exhaustive list of user actions for a feature.
+2. **State**: Simple POJO (kotlin data class) representing various UI states of the application.
+3. **SingleEventProducer**: This is a function which is invoked by the reducer to publish single events (that can/should only be consumed once like displaying toast, snackbar message or sending an analytics event). This function takes action, effect, current state as input and it returns a `SingleEvent`. By default this function is null for any Feature.
### Architecture Overview of PrivacyCentral App
@@ -50,179 +48,6 @@ Looking at the diagram from right to left:
8. **ViewModel**: arch-component lifecycle aware viewmodel.
9. **Views**: Android high level components like activities, fragments, etc.
-## How to implement a new feature
-Imaging you have to implement a fake location feature.
-1. Create a new package under `features` called `fakelocation`
-2. Create a new feature class called `FakeLocationFeature` and make it extend the BaseFeature class as below:
-```kotlin
-class FakeLocationFeature(
- initialState: State,
- coroutineScope: CoroutineScope,
- reducer: Reducer<State, Effect>,
- actor: Actor<State, Action, Effect>,
- singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent>
-) : BaseFeature<FakeLocationFeature.State, FakeLocationFeature.Action, FakeLocationFeature.Effect, FakeLocationFeature.SingleEvent>(
- initialState,
- actor,
- reducer,
- coroutineScope,
- { message -> Log.d("FakeLocationFeature", message) },
- singleEventProducer
-) {
- // Other elements goes here.
-}
-```
-
-3. Define various elements for the feature in the above class
-```kotlin
-// State to be reflected in the UI
-data class State(val location: Location)
-
-// User triggered actions
-sealed class Action {
- data class UpdateLocationAction(val latLng: LatLng) : Action()
- object UseRealLocationAction : Action()
- object UseSpecificLocationAction : Action()
- data class SetFakeLocationAction(val latitude: Double, val longitude: Double) : Action()
-}
-
-// Output from the actor after processing an action
-sealed class Effect {
- data class LocationUpdatedEffect(val latitude: Double, val longitude: Double) : Effect()
- object RealLocationSelectedEffect : Effect()
- ...
- ...
- data class ErrorEffect(val message: String) : Effect()
-}
-```
-
-4. Create a static `create` function in feature which returns the feature instance:
-```kotlin
-companion object {
- fun create(
- initialState: State = <initial state>
- coroutineScope: CoroutineScope
- ) = FakeLocationFeature(
- initialState, coroutineScope,
- reducer = { state, effect ->
- when (effect) {
- Effect.RealLocationSelectedEffect -> state.copy(
- location = state.location.copy(
- mode = LocationMode.REAL_LOCATION
- )
- )
- is Effect.ErrorEffect, Effect.SpecificLocationSavedEffect -> state
- is Effect.LocationUpdatedEffect -> state.copy(
- location = state.location.copy(
- latitude = effect.latitude,
- longitude = effect.longitude
- )
- )
- }
- },
- actor = { _, action ->
- when (action) {
- is Action.UpdateLocationAction -> flowOf(
- Effect.LocationUpdatedEffect(
- action.latLng.latitude,
- action.latLng.longitude
- )
- )
- is Action.SetFakeLocationAction -> {
- val location = Location(
- LocationMode.CUSTOM_LOCATION,
- action.latitude,
- action.longitude
- )
- // TODO: Call fake location api with specific coordinates here.
- val success = DummyDataSource.setLocationMode(
- LocationMode.CUSTOM_LOCATION,
- location
- )
- if (success) {
- flowOf(
- Effect.SpecificLocationSavedEffect
- )
- } else {
- flowOf(
- Effect.ErrorEffect("Couldn't select location")
- )
- }
- }
- Action.UseRealLocationAction -> {
- // TODO: Call turn off fake location api here.
- val success = DummyDataSource.setLocationMode(LocationMode.REAL_LOCATION)
- if (success) {
- flowOf(
- Effect.RealLocationSelectedEffect
- )
- } else {
- flowOf(
- Effect.ErrorEffect("Couldn't select location")
- )
- }
- }
- Action.UseSpecificLocationAction -> {
- flowOf(Effect.SpecificLocationSelectedEffect)
- }
- }
- },
- singleEventProducer = { _, _, effect ->
- when (effect) {
- Effect.SpecificLocationSavedEffect -> SingleEvent.SpecificLocationSavedEvent
- Effect.RealLocationSelectedEffect -> SingleEvent.RealLocationSelectedEvent
- is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message)
- else -> null
- }
- }
- )
- }
-```
-
-5. Create a `viewmodel` like below:
-```kotlin
-class FakeLocationViewModel : ViewModel() {
-
- private val _actions = MutableSharedFlow<FakeLocationFeature.Action>()
- val actions = _actions.asSharedFlow()
-
- val fakeLocationFeature: FakeLocationFeature by lazy {
- FakeLocationFeature.create(coroutineScope = viewModelScope)
- }
-
- fun submitAction(action: FakeLocationFeature.Action) {
- viewModelScope.launch {
- _actions.emit(action)
- }
- }
-}
-```
-
-6. Create a `fragment` for your feature and make sure it implements `MVIView<>` interface
-7. Initialize (or retrieve the existing) instance of viewmodel in your `fragment` class by using extension function.
-```kotlin
-private val viewModel: FakeLocationViewModel by viewModels()
-```
-
-8. In `onCreate` method of fragment, launch a coroutine to bind the view to feature and to listen single events.
-```kotlin
-override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- lifecycleScope.launchWhenStarted {
- viewModel.fakeLocationFeature.takeView(this, this@FakeLocationFragment)
- }
- lifecycleScope.launchWhenStarted {
- viewModel.fakeLocationFeature.singleEvents.collect { event ->
- // Do something with event
- }
- }
-}
-```
-
-9. To render the state in UI, override the `render` function of MVIView.
-10. For publishing ui actions, use `viewModel.submitAction(action)`.
-
-Everything is lifecycle aware so we don't need to anything manually here.
## Code Quality and Style
This project integrates a combination of unit tests, functional test and code styling tools.
To run **unit** tests on your machine:
@@ -240,13 +65,10 @@ To run code style check and formatting tool:
The project currently doesn't have exactly the same mentioned structure as it is just a POC and will be improved.
### Todo/Improvements
-- [ ] Add domain layer with usecases.
-- [ ] Add data layer with repository implementation.
- [ ] Add unit tests and code coverage.
- [ ] Implement Hilt DI.
# References
1. [Kotlin Flow](https://kotlinlang.org/docs/flow.html)
2. [MVI](https://hannesdorfmann.com/android/mosby3-mvi-1/)
-3. [Redux](https://redux.js.org/)
4. [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) \ No newline at end of file