Skip to content

Commit

Permalink
Migrate lib to coroutines and remove RX dependencies
Browse files Browse the repository at this point in the history
This PR aims to update the master branch of the repo with the changes made in the masmovil forked repo.
It contains major changes related to a library migration to Kotlin coroutines instead of RX. Copyright information in all necessary classes has also updated.

Co-authored-by: yamidragut <[email protected]>
Co-authored-by: Adrián García <[email protected]>
  • Loading branch information
3 people authored Jul 22, 2021
1 parent fba1ad2 commit 8faa856
Show file tree
Hide file tree
Showing 99 changed files with 2,609 additions and 1,700 deletions.
9 changes: 7 additions & 2 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
# Each line is a file pattern followed by one or more owners.

# Current code owners
# @adriangl -> Adrián García
# - @nicolasmertanen -> Nicolás Mertanen
# - @finxo -> Alejandro Lopez
# - @FrangSierra -> Francisco García
# - @adriangl -> Adrián García
# - @yamidragut -> Estefanía Sarasola
# - @Babelia13 -> Sara Lucía Pérez

* @adriangl
* @nicolasmertanen @finxo @FrangSierra @adriangl @yamidragut @Babelia13
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
# Updates for Gradle dependencies used in the app
- package-ecosystem: gradle
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ hs_err_pid*
# Icon must end with two \r
Icon


# Thumbnails
._*

Expand Down
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security
- No security issues fixed!

## [3.0.0] - 2021-06-10
### Changed
- BREAKING CHANGE: Remove RX packages, moved API to full coroutines.
### Changed
- Add support for injecting view models scoped to the navigation component's graph in Jetpack Compose.

## [2.0.0] - 2021-05-01
### Changed
- Changed repo ownership to [hyperdevs-team](https://github.com/hyperdevs-team). Thanks [bq](https://github.com/bq) for all the work!
Expand All @@ -33,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.3.2] - 2020-05-27
### Added
- Add `allTerminal`, `onAllTerminal` and `firstExceptionOrNull` functions to lists of `Resource`s
- Add `allTerminal`, `onAllTerminal` and `firstExceptionOrNull` functions to lists of `Resource`s.

## [1.3.1] - 2020-04-22
### Added
Expand Down Expand Up @@ -114,7 +120,8 @@ state (`success` or `failure`)
### Added
- Initial architecture release.

[Unreleased]: https://github.com/hyperdevs-team/mini-kotlin/compare/2.0.0...HEAD
[Unreleased]: https://github.com/hyperdevs-team/mini-kotlin/compare/3.0.0...HEAD
[3.0.0]: https://github.com/hyperdevs-team/mini-kotlin/compare/2.0.0...3.0.0
[2.0.0]: https://github.com/hyperdevs-team/mini-kotlin/compare/1.4.0...2.0.0
[1.4.0]: https://github.com/hyperdevs-team/mini-kotlin/compare/1.3.3...1.4.0
[1.3.3]: https://github.com/hyperdevs-team/mini-kotlin/compare/1.3.2...1.3.3
Expand Down
118 changes: 81 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Mini
[![Release](https://jitpack.io/v/hyperdevs-team/mini-kotlin.svg)](https://jitpack.io/#hyperdevs-team/mini-kotlin)

## ⚠️ DOCS HAVE TO BE UPDATED FOR VERSION 3.x.y

Mini is a minimal Flux architecture written in Kotlin that also adds a mix of useful features to build UIs fast.

## Purpose
Expand All @@ -9,24 +11,24 @@ Feature development using Mini is fast compared to traditional architectures (li

## How to Use
### Dispatcher
The *Dispatcher* is the hub that manages all data flow in a Flux application. It is basically a holder of store callbacks: each store registers itself and provides a callback for an action.
The *Dispatcher* is the hub that manages all data flow in a Flux application. It is basically a holder of store callbacks: each store registers itself and provides a callback for an action.

One important thing is that the dispatching is always performed in the same thread to avoid possible side-effects.

We can dispatch actions in the following ways:

```kotlin
// Dispatch an action on the main thread synchronously
dispatcher.dispatch(LoginAction(username = "user", password = "123"))
dispatcher.dispatch(action = LoginAction(username = "user", password = "123"))

// Post an event that will dispatch the action on the UI thread and return immediately.
dispatcher.dispatchAsync(LoginAction(username = "user", password = "123"))
// Dispatch an action on the given scope
dispatcher.dispatchOn(action = LoginAction(username = "user", password = "123"), scope = coroutineScope)
```

### Store
The *Stores* are holders for application state and state mutation logic. In order to do so they expose pure reducer functions that are later invoked by the dispatcher.
The *Stores* are holders for application state and state mutation logic. In order to do so they expose pure reducer functions that are later invoked by the dispatcher. A *Store* is a type of a *StateContainer*, which is exactly that: a container of states.

The state is a plain object (usually a `data class`) that holds all information needed to display the view. States should always be inmutable. State classes should avoid using framework elements (View, Camera, Cursor...) in order to facilitate testing.
The state is a plain object (usually a `data class`) that holds all information needed to display the view. States should always be immutable. State classes should avoid using framework elements (View, Camera, Cursor...) in order to facilitate testing.

Stores subscribe to actions to change the application state after a dispatch. Mini generates the code that links dispatcher actions and stores using the `@Reducer` annotation over a **non-private function that receives an Action as parameter**.

Expand All @@ -52,43 +54,38 @@ An *Action* is a simple class that usually represents a use case. It can also co

For example, we may want to log in to a service. We would create an action like this one:
```kotlin
@Action
data class LoginAction(val username: String, val password: String)
```

When we receive the response from the server, we'll dispatch another action with the result:
```kotlin
@Action
data class LoginCompleteAction(val loginTask: Task, val user: User?)
```

Actions will usually be triggered from Views or Controllers.

### View changes
Each ``Store`` exposes a custom `StoreCallback` though the method `observe` or a `Flowable` if you want to make use of RxJava. Both of them emits changes produced on their states, allowing the view to listen reactive the state changes. Being able to update the UI according to the new `Store` state.
Each ``StateContainer`` exposes a Kotlin `Flow` that emits changes produced on the state, allowing the view to listen reactive those changes. Being able to update the UI according to the new `StateContainer` state.

```kotlin
//Using RxJava
userStore
.flowable()
.map { it.name }
.subscribe { updateUserName(it) }

// Custom callback
userStore
.observe { state -> updateUserName(state.name) }
```kotlin
mainStore.flow()
.onEach { state ->
// Do whatever you want
}.launchInLifecycleScope()
```

If you make use of the RxJava methods, you can make use of the `SubscriptionTracker` interface to keep track of the `Disposables` used on your activities and fragments.

### Tasks
A `Task` is a basic object to represent an ongoing process. They should be used in the state of our `Store` to represent ongoing processes that must be represented in the UI.
A `Task` is a basic object to represent an ongoing process. They should be used in the state of our `StateContainer` (a `Store`, for example) to represent ongoing processes that must be represented in the UI.

You can also use `TypedTask` to save metadata related the current task.

**IMPORTANT: Do not use TypedTask to hold values that must survive multiple task executions. Save them as a variable in the state instead.**


### Example
Given the example Stores and Actions explained before, the workflow will be:
Given the example Stores and Actions explained before, the workflow would be:

- View dispatches `LoginAction`.
- Store changes his `LoginTask` status to running and call though his SessionController which will do all the async work to log in the given user.
Expand All @@ -97,12 +94,18 @@ Given the example Stores and Actions explained before, the workflow will be:
- The Store changes his state to the given values from `LoginCompleteAction`.
- The View redirect to the HomeActivity if the task was success or shows an error if not.

## Rx Utils
Mini includes some utility extensions over RxJava 2.0 to make easier listen state changes over the `Stores`.
You can execute another sample in the `app` package. It contains two different samples executing two types of `StateContainer`s:
- `StoreSampleActivity` class uses a `Store` as a `StateContainer`.
- `ViewModelSampleActivity` class uses a `ViewModel` as a `StateContainer`.

- `mapNotNull`: Will emit only not null values over the given `map` clause.
- `select`: Like `mapNotNull` but avoiding repeated values.
- `onNextTerminalState`: Used to map a `Task` inside an state and listen the next terminal state(Success - Error). Executing a different closure depending of the result of the task.
## Kotlin Flow Utils
Mini includes some utility extensions over Kotlin `Flow` to make easier listen state changes over the `StateContainer`s.

- `select`: Will emit only not null values over the given `map` clause.
- `selectNotNull`: Like `select` but avoiding null values.
- `onEachChange`: Emits a value when the values goes from one value to another.
- `onEachDisable`: Emits when the value goes from true to false.
- `onEachEnable`: Emits when the value goes from false to true.

## Navigation
To avoid loops over when working with navigation based on a process result. You will need to make use of `onNextTerminalState` after dispatch and `Action` that starts a process which result could navigate to a different screen.
Expand All @@ -120,11 +123,11 @@ For example:
If we continually listen the changes of a `Task` and we navigate to a specific screen when the `Task` becomes successful. The state will stay on SUCCESS and if we navigate back to the last screen we will be redirected again.

## Logging
Mini includes a custom `LoggerInterceptor` to log any change in your `Store` states produced from an `Action`. This will allow you to keep track of your actions, changes and side-effects more easily.
To add the LoggerInterceptor to your application you just need to add a single instance of it to your `Dispatcher` after initialize it in your `Application` class or dependency injection code.
Mini includes a custom `LoggerMiddleware` to log any change in your `StateContainer` states produced from an `Action`. This will allow you to keep track of your actions, changes and side-effects more easily.
To add the LoggerMiddleware to your application you just need to add a single instance of it to your `Dispatcher` after initialize it in your `Application` class or dependency injection code.
```kotlin
val loggerInterceptor = CustomLoggerInterceptor(stores().values)
dispatcher.addInterceptor(loggerInterceptor)
val loggerMiddleware = CustomLoggerMiddleware(stores().values)
dispatcher.addMiddleware(loggerMiddleware)
```

## Testing with Mini
Expand Down Expand Up @@ -184,18 +187,16 @@ Add the following dependencies to your app's `build.gradle`:

```groovy
dependencies {
def mini_version = "2.0.0"
def mini_version = "3.0.0"
// Minimum working dependencies
implementation "com.github.hyperdevs-team.mini-kotlin:mini-android:$mini_version"
kapt "com.github.hyperdevs-team.mini-kotlin:mini-processor:$mini_version"
// RxJava 2 helper libraries
implementation "com.github.hyperdevs-team.mini-kotlin:mini-rx2:$mini_version"
implementation "com.github.hyperdevs-team.mini-kotlin:mini-rx2-android:$mini_version"
// Kodein helper libraries
implementation "com.github.hyperdevs-team.mini-kotlin:mini-kodein:$mini_version"
implementation "com.github.hyperdevs-team.mini-kotlin:mini-kodein-android:$mini_version"
// Kodein helper library for view models scoped to the Navigation component's graph in Jetpack Compose
implementation "com.github.hyperdevs-team.mini-kotlin:mini-kodein-android-compose:$mini_version"
// Testing helper libraries
androidTestImplementation "com.github.hyperdevs-team.mini-kotlin:mini-testing:$mini_version"
Expand Down Expand Up @@ -241,12 +242,55 @@ stores.forEach { store ->
store.initialize()
}

// Optional: add logging interceptor to log action events
dispatcher.addInterceptor(LoggerInterceptor(stores)) { tag, msg ->
// Optional: add logging middleware to log action events
dispatcher.addMiddleware(LoggerMiddleware(stores)) { tag, msg ->
Log.d(tag, msg)
}
```

### \[Android] Kodein Android utils
`mini-kodein-android` has some utility methods in order to inject an Android's `ViewModel` in a `DIAware` `Activity` or `Fragment`.
In order to use them, bind the Android's `ViewModelProvider.Factory` instance with Kodein:
```kotlin
// Use any tag to differ between the injected `Context` or `Application` if you are binding also `Context` with Kodein
bind<Application>("appTag") with singleton { app }
bind<ViewModelProvider.Factory>() with singleton { DIViewModelFactory(di.direct) }
```
To inject a ViewModel without parameters, bind it as follows:
```kotlin
bindViewModel { MainViewModel(instance("appTag") }
```
And in your `DIAware` `Activity` or `Fragment`:
```kotlin
private val mainViewModel: MainViewModel by viewModel()
```

`mini-kodein-android-compose` has some utility methods in order to inject an Android's `ViewModel` in the scope of a Navigation component
graph. This is useful as in Jetpack Compose it is common to have only one or few `Activities` and no `Fragment`s so, in order to scope
the lifecycle of the `ViewModel` not for all the life of the `Activity`, we can scope it to any route existing in the `NavBackStackEntry`.
In order to use it, do the same as above, but instead of injecting the ViewModel scoped to a route of the Navigation, the `NavHost` composable
must be inside an `DIAware Activity`, and then do as follows:
```kotlin
composable(route = "home") { navBackStackEntry ->
val homeViewModel: HomeViewModel by navBackStackEntry.viewModel(contextDI())
HomeScreen(homeViewModel, ...)
}
```
In case you want to pass an argument to a ViewModel, you need to bind the factory of that kind of Android's ViewModel.
You can do this in both `mini-kodein-android`, and `mini-kodein-android-compose`.
For example, given a `ViewModel` that you want to pass a `String` param, it would be:
```kotlin
bindViewModelFactory<HomeViewModelWithParameter, ViewModelProvider.Factory> { param ->
TypedViewModelFactory(HomeViewModelWithParameter::class, instance("appTag"), param as String)
}
```
And to retrieve it with the given param in its constructor:
```kotlin
val param = "Hello World!"
val homeViewModelWithParameter: HomeViewModelWithParameter by navBackStackEntry.viewModel(contextDI(), param)
```

### \[Android] Proguard/R8
Each of the libraries contain a sensible proguard file that your project can consume in order to run you app on Proguard or R8.
No additional steps have to be done in order to use them apart from enabling minify in your project.
Expand Down
Loading

0 comments on commit 8faa856

Please sign in to comment.