Comment dĂ©mĂȘler la jungle MVI en utilisant notre propre Jungle de production et obtenir une solution architecturale simple et structurĂ©e.
Préface
Quand je suis tombĂ© sur un article sur Model-View-Intent (MVI) pour Android, je ne l'ai mĂȘme pas ouvert. 
      
        
        
        
      
    
 - Sérieusement!? Architecture sur Android Intents?
. MVI , .
MVI, , - , - . , MVP MVVM, , , : " ?".
, , - ; , .
. ( ):
- ;
 - UI , ;
 - .
 
?
â (Jungle). â RxJava .
- State â "" UI, View;
 - Action â "" UI, View (, Snackbar Toast);
 - Event â Intent Model-View-Intent;
 - MviView â , Actions State;
 - Middleware â UI;
 - Store â Model View, , Events, State Actions.
 
      
        
        
        
      
    
 ,   , â 
?
, â . , , . :
- PrgoressBar ;
 - Button Toast ;
 - , ;
 - , - .
 
UI :
sealed class DemoEvent {
   object Load : DemoEvent()
}
      sealed class DemoAction {
   data class ShowError(val error: String) : DemoAction()
}
      data class DemoState(
   val loading: Boolean = false,
   val countries: List<Country> = emptyList()
)
      class DemoFragment : Fragment, MviView<DemoState, DemoAction> {
   private lateinit var demoStore: DemoStore
   private var adapter: DemoAdapter? = null
   /*Initializations are skipped*/
   override fun onViewCreated(view: View, bundle: Bundle?) {
      super.onViewCreated(view, bundle)
      demoStore.run {
         attach(this@DemoFragment)
         dispatchEventSource(
            RxView.clicks(demo_load)
               .map { DemoEvent.Load }
         )
      }
   }
   override fun onDestroyView() {
      super.onDestroyView()
      demoStore.detach()
   }
   override fun render(state: DemoState) {
      val showReload = state.run {
         !loading && countries.isEmpty()
      }
      demo_load.visibility = if (showReload)
         View.GONE else
         View.VISIBLE
      demo_progress.visibility = if (state.loading)
         View.VISIBLE else
         View.GONE
      demo_recycler.visibility = if (state.countries.isEmpty())
         View.GONE else
         View.VISIBLE
      adapter?.apply {
         setItems(state.countries)
         notifyDataSetChanged()
      }
   }
   override fun processAction(action: DemoAction) {
      when (action) {
         is DemoAction.ShowError ->
            Toast.makeText(
               requireContext(),
               action.error,
               Toast.LENGTH_SHORT
            ).show()
      }
   }
}
      () ? DemoEvent.Load DemoStore ( Reload ); DemoAction.ShowError ( ) Toast; DemoState ( ) UI . .
DemoStore. , Store, DemoEvent, DemoAction DemoState:
class DemoStore (
   foregroundScheduler: Scheduler,
   backgroundScheduler: Scheduler
) : Store<DemoEvent, DemoState, DemoAction>(
   foregroundScheduler = foregroundScheduler,
   backgroundScheduler = backgroundScheduler
)
      , CountryMiddleware, :
class CountryMiddleware(
   private val getCountriesInteractor: GetCountriesInteractor
) : Middleware<CountryMiddleware.Input>() {
   override val inputType = Input::class.java
   override fun transform(upstream: Observable<Input>) =
      upstream.switchMap<CommandResult> {
         getCountriesInteractor.execute()
            .map<Output> { Output.Loaded(it) }
            .onErrorReturn {
               Output.Failed(it.message ?: "Can't load countries")
            }
            .startWith(Output.Loading)
      }
   object Input : Command
   sealed class Output : CommandResult {
      object Loading : Output()
      data class Loaded(val countries: List<Country>) : Output()
      data class Failed(val error: String) : Output()
   }
}
      Command? , "-" . CommandResult? "-".
   CountryMiddleware.Input ,   CountryMiddleware   .    Middleware  CommandResult;          sealed  (CountryMiddleware.Output).
Observable, Output.Loading , Output.Loaded , Output.Failed .
DemoStore CountryMiddleware Reload :
class DemoStore (..., countryMiddleware: CountryMiddleware) ... {
   override val middlewares = listOf(countryMiddleware)
   override fun convertEvent(event: DemoEvent) = when (event) {
      is DemoEvent.Load -> CountryMiddleware.Input
   }
}
        middlewares  ,  Middlewares  DemoStore  .   Store  Commands.      DemoEvent.Load  CountryMiddleware.Input ( ,   ).
, CountryMiddleware. DemoState:
class DemoStore ... {
   ...
   override val initialState = DemoState()
   override fun reduceCommandResult(
      state: DemoState,
      result: CommandResult
   ) = when (result) {
      is CountryMiddleware.Output.Loading ->
         state.copy(loading = true)
      is CountryMiddleware.Output.Loaded ->
         state.copy(loading = false, countries = result.countries)
      is CountryMiddleware.Output.Failed ->
         state.copy(loading = false)
      else -> state
   }
}
         State,       initialState.     reduceCommandResult   ,   CommandResult  State.
DemoAction.ShowError. , Command ( CommandResult) Action:
class DemoStore ... {
   ...
   override fun produceCommand(commandResult: CommandResult) =
      when (commandResult) {
         is CountryMiddleware.Output.Failed ->
            ProduceActionCommand.Error(commandResult.error)
         else -> null
      }
   override fun produceAction(command: Command) =
      when (command) {
         is ProduceActionCommand.Error ->
            DemoAction.ShowError(command.error)
         else -> null
      }
   sealed class ProduceActionCommand : Command {
      data class Error(val error: String) : ProduceActionCommand()
   }
}
      ,    â     CountryMiddleware. ,   ,    Command  bootstrapCommands:
class DemoStore ... {
   ...
   override val bootstrapCommands = listOf(CountryMiddleware.Input)
}
      !
?
, , - . . Store, Middlewares, MviView.
 View     -   ?     Events,  Store, Middleware    render   MviView.
, - ? , Event Store .
, , , .
?
, , :
-  Commands  
sealedStore, : Actions State? - Commands, Middlewares, .
 
, Middleware â , UseCase (Interactor). , (, , - domain layer) . , , Middleware .
, . , SingleLiveEvent Actions.
wiki. . , !