"use strict";(self.webpackChunkthinkrchive_multiplatform=self.webpackChunkthinkrchive_multiplatform||[]).push([[753],{3905:function(e,t,n){n.d(t,{Zo:function(){return p},kt:function(){return d}});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?a(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):a(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,r,i=function(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=r.createContext({}),c=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),h=c(n),d=i,m=h["".concat(l,".").concat(d)]||h[d]||u[d]||a;return n?r.createElement(m,o(o({ref:t},p),{},{components:n})):r.createElement(m,o({ref:t},p))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:i,o[1]=s;for(var c=2;c<a;c++)o[c]=n[c];return r.createElement.apply(null,o)}return r.createElement.apply(null,n)}h.displayName="MDXCreateElement"},6033:function(e,t,n){n.r(t),n.d(t,{frontMatter:function(){return s},contentTitle:function(){return l},metadata:function(){return c},assets:function(){return p},toc:function(){return u},default:function(){return d}});var r=n(7462),i=n(3366),a=(n(7294),n(3905)),o=["components"],s={sidebar_position:1},l="Introduction",c={unversionedId:"architecture/intro",id:"architecture/intro",title:"Introduction",description:"This app uses the Model View Intent (MVI) architecture.",source:"@site/docs/architecture/intro.md",sourceDirName:"architecture",slug:"/architecture/intro",permalink:"/Thinkrchive-Multiplatform/architecture/intro",editUrl:"https://github.com/Thinkrchive/Thinkrchive-Multiplatform-Docs/blob/main/docs/architecture/intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",previous:{title:"Extras",permalink:"/Thinkrchive-Multiplatform/overview/extras"}},p={},u=[{value:"Orbit MVI",id:"orbit-mvi",level:2},{value:"Structure",id:"structure",level:3},{value:"Project Structure",id:"project-structure",level:2}],h={toc:u};function d(e){var t=e.components,s=(0,i.Z)(e,o);return(0,a.kt)("wrapper",(0,r.Z)({},h,s,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"introduction"},"Introduction"),(0,a.kt)("p",null,"This app uses the Model View Intent (MVI) architecture."),(0,a.kt)("p",null,"MVI is an architectural pattern that utilizes unidirectional data flow."),(0,a.kt)("p",null,"In MVI each component can be described as follows;"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"Model - It contains state of the UI. This has various UI states like Loading, Loaded, List Data, Error, etc. The UI just subscribes to these states and never tries to alter them directly.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"View - Is all the UI related code that binds our states to the various UI components like Activities, Fragements, Composables and other UI components.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"Intent - This is what describes a user action. This is the only way to communicate with our Model layer so it can process the user action and return a state."))),(0,a.kt)("p",null,"You can see that data only flow in one direction. Intents define the action needed, provided to the Model so it can process the data and produce a state that will be returned to the View and be displayed to the user. It's not so different from MVVM. But with MVI we make sure that a view only displays state and nothing else. We can use ViewModels for process death handling but unlike MVVM they will only contain state provided by Model and won't create state themselves."),(0,a.kt)("p",null,(0,a.kt)("img",{loading:"lazy",alt:"MVI Structure",src:n(4075).Z,width:"652",height:"408"})),(0,a.kt)("p",null,"MVI is the perfect choice for a Kotlin Multiplatform project as it helps you separate as much code from the UI as possible. You can have native UI on all platform that all consume states provided by MVI without having to do any manipulatio logic on it."),(0,a.kt)("h2",{id:"orbit-mvi"},"Orbit MVI"),(0,a.kt)("p",null,"This project uses ",(0,a.kt)("a",{parentName:"p",href:"https://orbit-mvi.org/"},"Orbit MVI")," for it's MVI implementation."),(0,a.kt)("p",null,"Obrbit MVI takes a more MVVM approach in MVI instead of the Redux route most other libraries like ",(0,a.kt)("a",{parentName:"p",href:"https://arkivanov.github.io/MVIKotlin/"},"MVIKotlin")," use."),(0,a.kt)("p",null,"Orbit MVI while not taking separation on concerns to the extreme like MVIKotlin it let's create your MVI system without a lot of boiler plate code"),(0,a.kt)("p",null,"This makes development using Orbit very quick and does not have such a steep learning curve for developers who have already worked with MVVM before."),(0,a.kt)("h3",{id:"structure"},"Structure"),(0,a.kt)("p",null,(0,a.kt)("img",{loading:"lazy",alt:"Orbit MVI Structure",src:n(242).Z,width:"654",height:"101"})),(0,a.kt)("p",null,"Orbit MVI works as follows:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"UI invokes functions on a class implementing the ContainerHost interface like ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/Thinkrchive/Thinkrchive-Multiplatform/blob/main/common/integration/src/commonMain/kotlin/work.racka.thinkrchive.v2.common.integration/containers/list/ThinkpadListContainerHost.kt"},"this"),". Typically in Android this might be an Activity, Fragment or a simple View. However, an Orbit system can also be run without any UI, for example as a background service.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"The functions call through to a Container instance through the ",(0,a.kt)("inlineCode",{parentName:"p"},"intent")," block which offloads work to a background coroutine and provides a DSL for side effects and reductions.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"Transformations are performed through user-defined business logic within the ",(0,a.kt)("inlineCode",{parentName:"p"},"intent")," block.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"The reduce operator reduces the current state of the system with the incoming events to produce new states.")),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("p",{parentName:"li"},"The new state is sent to observers."))),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},(0,a.kt)("em",{parentName:"strong"},"Source: ",(0,a.kt)("a",{parentName:"em",href:"https://orbit-mvi.org/Core/architecture"},"Orbit MVI docs")))),(0,a.kt)("p",null,"On top of providing states, Orbit MVI also has the concept of ",(0,a.kt)("strong",{parentName:"p"},(0,a.kt)("em",{parentName:"strong"},"Side Effects")),"\nSide effects are one time events that are fired when a certain ",(0,a.kt)("inlineCode",{parentName:"p"},"intent")," produces something that is not directly mapped to the state. A side effect can be something like showing a Toast, Navigating to another page, Error/Loading Indicators when indirectly accessing Network calls, etc."),(0,a.kt)("p",null,(0,a.kt)("img",{loading:"lazy",alt:"Orbit MVI with Side Effects",src:n(341).Z,width:"721",height:"179"})),(0,a.kt)("p",null,"You can leverage the power of side effects to make calls to things that are not directly related to UI state. These should not post back any events to the UI."),(0,a.kt)("div",{className:"admonition admonition-tip alert alert--success"},(0,a.kt)("div",{parentName:"div",className:"admonition-heading"},(0,a.kt)("h5",{parentName:"div"},(0,a.kt)("span",{parentName:"h5",className:"admonition-icon"},(0,a.kt)("svg",{parentName:"span",xmlns:"http://www.w3.org/2000/svg",width:"12",height:"16",viewBox:"0 0 12 16"},(0,a.kt)("path",{parentName:"svg",fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"}))),"Example")),(0,a.kt)("div",{parentName:"div",className:"admonition-content"},(0,a.kt)("p",{parentName:"div"},"When you save a ",(0,a.kt)("inlineCode",{parentName:"p"},"Theme")," property in Settings you will provide ",(0,a.kt)("inlineCode",{parentName:"p"},"state")," to the UI to update the user and show them what ",(0,a.kt)("inlineCode",{parentName:"p"},"Theme")," they have selected. But to change the ",(0,a.kt)("inlineCode",{parentName:"p"},"Theme")," you can post a ",(0,a.kt)("inlineCode",{parentName:"p"},"side effect")," that will change the App ",(0,a.kt)("inlineCode",{parentName:"p"},"Theme")," when the user selects a new ",(0,a.kt)("inlineCode",{parentName:"p"},"Theme"),". This is not directly mapped to ",(0,a.kt)("inlineCode",{parentName:"p"},"UI state")," and hence is a ",(0,a.kt)("inlineCode",{parentName:"p"},"side effect"),"."))),(0,a.kt)("h2",{id:"project-structure"},"Project Structure"),(0,a.kt)("p",null,(0,a.kt)("img",{loading:"lazy",alt:"Project MVI Structure",src:n(4929).Z,width:"1942",height:"1965"})))}d.isMDXComponent=!0},4929:function(e,t,n){t.Z=n.p+"assets/images/app-mvi-9a6cbe46fd07f1b03afde8e1e78250a7.svg"},4075:function(e,t,n){t.Z=n.p+"assets/images/mvi-9c404923ad07519d71212e540d65b699.jpg"},242:function(e,t,n){t.Z=n.p+"assets/images/orbit-mvi-overview-d7831b9ed034b5df0ffad9db17c3bb56.svg"},341:function(e,t,n){t.Z=n.p+"assets/images/orbit-mvi-side-effect-255730b09bfce54c8e59ed2f950ed3a7.svg"}}]);