SlideShare a Scribd company logo
Model View Intent on
Android
Cody Engel
Senior Android Engineer @ Yello
What is Model View Intent?
• Coined by André Staltz, Cycle.js creator
• Split main into multiple parts
• Similar idea to MVP, MVVM, MVC
Why Am I Here?
• Let’s Try Model-View-Intent With Android

goo.gl/3fPSWV
• Flax For Android - Reactive Concept

goo.gl/1JjSpN
• Hello Flax - A Reactive Architecture For
Android

goo.gl/rD2ZVw
The Intent
• Interpret user interactions
• Input: a view which exposes streams of
events
• Output: stream of actions
The Model
• Manage the state of the application
• Input: stream of actions
• Output: stream of state
The View
• Visually represent state from the Model
• Input: stream of state
• Output: stream of events
Model
Intent
MVI Data Flow
View
User Triggers
An Event
Intent Emits
An Action
Model Emits
New
State
View Renders
The State
CODE?
class DrawingActivity : AppCompatActivity() {
val compositeDisposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drawing)
}
override fun onResume() {
super.onResume()
val disposable = DrawingModel(DrawingIntentImpl(drawingView))
.getObservable()
.subscribe {
drawingView.render(it)
}
compositeDisposable.add(disposable)
}
override fun onStop() {
compositeDisposable.clear()
super.onStop()
}
}
onResume
onStop
DrawingModel(DrawingIntentImpl(drawingView))
.getObservable()
.subscribe {
drawingView.render(it)
}
The Model The Intent The View
Called Whenever
State Changes
Where “it” Is The
DrawingState
Emits Drawing State
Changes
interface DrawingIntent {
fun getTouches(): Observable<DrawingPoint>
fun getColorClicks(): Observable<Boolean>
fun getResetClicks(): Observable<Boolean>
}
Emits DrawingPoint
Whenever Touch
Event TriggeredEmits Boolean
Whenever Color Is
ClickedEmits Boolean
Whenever Reset Is
Clicked
class DrawingIntentImpl(private val drawingView: DrawingView): DrawingIntent {
override fun getTouches(): Observable<DrawingPoint> {
return drawingView.getMotionEvents()
.filter { it.action != MotionEvent.ACTION_UP }
.map { DrawingPoint(it.x, it.y, it.size, it.eventTime) }
}
override fun getColorClicks(): Observable<Boolean> = drawingView.getChangeColorClicks().map { true }
override fun getResetClicks(): Observable<Boolean> = drawingView.getResetClicks().map { true }
}
Maps Reset Clicks
To Boolean
Click Events Come
From DrawingView
Maps Motion Events
To DrawingPoint
Only Emit When
MotionEvent Is Not
ACTION_UP
Emits A New
DrawingPoint
Intent Takes A
DrawingView
class DrawingModel(drawingIntent: DrawingIntent) {
private val randomColor = RandomColor()
private val collectedDrawingPoints: ArrayList<DrawingPoint> = ArrayList()
private val defaultColor = "#607D8B"
private var previousState = DrawingState(emptyList(), defaultColor, false)
private val subject: Subject<DrawingState> = ReplaySubject.create()
init {
Observable.merge(
transformTouchesToState(drawingIntent.getTouches()),
transformColorClicksToState(drawingIntent.getColorClicks()),
transformResetClicksToState(drawingIntent.getResetClicks())
).subscribe {
if (previousState != it) {
previousState = it
subject.onNext(it)
}
}
}
fun getObservable(): Observable<DrawingState> = subject
private fun transformColorClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> {
// Transforms Color Clicks Into DrawingState
}
private fun transformResetClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> {
// Transforms Reset Clicks Into DrawingState
}
private fun transformTouchesToState(touches: Observable<DrawingPoint>): Observable<DrawingState> {
// Transforms Touch Events Into DrawingState
}
}
Model Takes A
DrawingIntent
Transform Intent
Actions To State
Transformations
Return DrawingState
Handles Emission Of
New State
Our Activity
Subscribes To This
data class DrawingState(
val drawingPoints: List<DrawingPoint>,
val drawingColor: String,
val redraw: Boolean
)
Collection Of
DrawingPoints To Draw
What Color Our Line
Should Be
Whether Or Not We Need
To Redraw The Drawing
private fun transformColorClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> {
return clicks.map {
DrawingState(collectedDrawingPoints, randomColor.get(previousState.drawingColor), true)
}
}
private fun transformResetClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> {
return clicks.map {
collectedDrawingPoints.clear()
DrawingState(collectedDrawingPoints, defaultColor, true)
}
}
Emits New Drawing State
With Random Color
Emits New DrawingState
With No DrawingPoints
private fun transformTouchesToState(touches: Observable<DrawingPoint>): Observable<DrawingState> {
return touches.map {
var emitState = previousState
collectedDrawingPoints.add(it)
if (collectedDrawingPoints.size >= 2) {
val currentDrawingPoint = collectedDrawingPoints.get(collectedDrawingPoints.size - 1)
val previousDrawingPoint = collectedDrawingPoints.get(collectedDrawingPoints.size - 2)
emitState = previousState.copy(
drawingPoints = listOf(previousDrawingPoint, currentDrawingPoint),
redraw = false
)
}
emitState
}
}
Mutates Our Previous
State To New State
Return Our
emitState
Only Emit New State If
More Than Two
DrawingPoints Exist
interface DrawingView {
fun getMotionEvents(): Observable<MotionEvent>
fun getChangeColorClicks(): Observable<Any>
fun getResetClicks(): Observable<Any>
}
Emits MotionEvent
Whenever User
Touches ScreenEmits Anytime The
User Clicks Change
Color
Emits Anytime The
User Clicks Reset
class DrawingViewImpl(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) :
LinearLayout(context, attrs, defStyleAttr, defStyleRes), DrawingView {
private val paint = Paint()
private val strokeWidth = 10f
private var cachedBitmap: Bitmap? = null
private val changeColorButton: Button
private val resetButton: Button
override fun getMotionEvents(): Observable<MotionEvent> = RxView.touches(this)
override fun getChangeColorClicks(): Observable<Any> = RxView.clicks(changeColorButton)
override fun getResetClicks(): Observable<Any> = RxView.clicks(resetButton)
fun render(drawingState: DrawingState) {
// Renders Our View Based On Drawing State
}
private fun generateDrawingPath(drawingPoints: List<DrawingPoint>): Path {
// Generates The Drawing Path, Called By Render
}
}
Maps To User
Interactions
Renders Our
ViewState
Helper Used To
Generate
Drawing Path
fun render(drawingState: DrawingState) {
if (!isAttachedToWindow) return
if (cachedBitmap == null || drawingState.redraw) {
cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
}
paint.color = Color.parseColor(drawingState.drawingColor)
val drawingCanvas = Canvas(cachedBitmap)
drawingCanvas.drawPath(generateDrawingPath(drawingState.drawingPoints), paint)
background = BitmapDrawable(resources, cachedBitmap)
}
New Bitmap When
We Need To Redraw
Update The Color Of
Our Drawing Line
Draw Our Line Based On
generateDrawingPath
private fun generateDrawingPath(drawingPoints: List<DrawingPoint>): Path {
val drawingPath = Path()
var previousPoint: DrawingPoint? = null
for (currentPoint in drawingPoints) {
previousPoint?.let {
if (currentPoint.time - it.time < 25) {
drawingPath.quadTo(it.x, it.y, currentPoint.x, currentPoint.y)
} else {
drawingPath.moveTo(currentPoint.x, currentPoint.y)
}
} ?: drawingPath.moveTo(currentPoint.x, currentPoint.y)
previousPoint = currentPoint
}
return drawingPath
}
Iterates Over The
DrawingPoints To
Generate A Path
• https://github.com/CodyEngel/MVI
• https://medium.com/@CodyEngel

More Related Content

PDF
Enhancing UI/UX using Java animations
PDF
Standford 2015 week7: 1. Unwind Segues, Alerts, Timers, View Animation 2. Dyn...
PDF
QML\Qt Quick на практике
PDF
Getting Started With Material Design
PDF
Standford 2015 week4: 1.Protocols and Delegation, Gestures 2. Multiple MVCs
PPTX
google play service 7.8 & new tech in M
PDF
Building Apps with Flutter - Hillel Coren, Invoice Ninja
PPT
Working with Callbacks
Enhancing UI/UX using Java animations
Standford 2015 week7: 1. Unwind Segues, Alerts, Timers, View Animation 2. Dyn...
QML\Qt Quick на практике
Getting Started With Material Design
Standford 2015 week4: 1.Protocols and Delegation, Gestures 2. Multiple MVCs
google play service 7.8 & new tech in M
Building Apps with Flutter - Hillel Coren, Invoice Ninja
Working with Callbacks

What's hot (9)

PDF
14multithreaded Graphics
PDF
MVI - Managing State The Kotlin Way
PDF
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
PDF
Introduction to Canvas - Toronto HTML5 User Group
PPTX
Introduction to Canvas - Toronto HTML5 User Group
PDF
Awesome State Management for React and Other Virtual-Dom Libraries
PDF
The Ring programming language version 1.7 book - Part 59 of 196
PPTX
Intro to Canva
PDF
The Ring programming language version 1.6 book - Part 57 of 189
14multithreaded Graphics
MVI - Managing State The Kotlin Way
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
Introduction to Canvas - Toronto HTML5 User Group
Introduction to Canvas - Toronto HTML5 User Group
Awesome State Management for React and Other Virtual-Dom Libraries
The Ring programming language version 1.7 book - Part 59 of 196
Intro to Canva
The Ring programming language version 1.6 book - Part 57 of 189
Ad

Similar to Model View Intent on Android (20)

PDF
Architectures in the compose world
PDF
Reduxing UI: Borrowing the Best of Web to Make Android Better
PDF
Building Testable Reactive Apps with MVI
PDF
Gabor Varadi - Reactive State Management with Jetpack Components
PPTX
Reactive state management with Jetpack Components
PDF
Working effectively with ViewModels and TDD - UA Mobile 2019
PDF
Breathing the life into the canvas
PPTX
Kotlin Mullets
PPTX
It's the arts! Playing around with the Android canvas
PDF
Survive the lifecycle
PDF
S'il te plait, dessine moi une vue
PDF
Creating custom views
PDF
Journey of an event, the android touch - Marco Cova, Facebook
PDF
Reuse features in Android applications
PDF
What’s new in Android JetPack
PDF
Oleksandr Tolstykh
PDF
Android Jetpack: ViewModel and Testing
DOCX
Android canvas-chapter20
PDF
MVM - It's all in the (Implementation) Details
PDF
Canvas API in Android
Architectures in the compose world
Reduxing UI: Borrowing the Best of Web to Make Android Better
Building Testable Reactive Apps with MVI
Gabor Varadi - Reactive State Management with Jetpack Components
Reactive state management with Jetpack Components
Working effectively with ViewModels and TDD - UA Mobile 2019
Breathing the life into the canvas
Kotlin Mullets
It's the arts! Playing around with the Android canvas
Survive the lifecycle
S'il te plait, dessine moi une vue
Creating custom views
Journey of an event, the android touch - Marco Cova, Facebook
Reuse features in Android applications
What’s new in Android JetPack
Oleksandr Tolstykh
Android Jetpack: ViewModel and Testing
Android canvas-chapter20
MVM - It's all in the (Implementation) Details
Canvas API in Android
Ad

Recently uploaded (6)

PDF
6-UseCfgfhgfhgfhgfhgfhfhhaseActivity.pdf
DOC
证书学历UoA毕业证,澳大利亚中汇学院毕业证国外大学毕业证
PDF
heheheueueyeyeyegehehehhehshMedia-Literacy.pdf
PDF
Lesson 13- HEREDITY _ pedSAWEREGFVCXZDSASEWFigree.pdf
DOC
Camb毕业证学历认证,格罗斯泰斯特主教大学毕业证仿冒文凭毕业证
PPTX
ASMS Telecommunication company Profile
6-UseCfgfhgfhgfhgfhgfhfhhaseActivity.pdf
证书学历UoA毕业证,澳大利亚中汇学院毕业证国外大学毕业证
heheheueueyeyeyegehehehhehshMedia-Literacy.pdf
Lesson 13- HEREDITY _ pedSAWEREGFVCXZDSASEWFigree.pdf
Camb毕业证学历认证,格罗斯泰斯特主教大学毕业证仿冒文凭毕业证
ASMS Telecommunication company Profile

Model View Intent on Android

  • 1. Model View Intent on Android Cody Engel Senior Android Engineer @ Yello
  • 2. What is Model View Intent? • Coined by André Staltz, Cycle.js creator • Split main into multiple parts • Similar idea to MVP, MVVM, MVC
  • 3. Why Am I Here? • Let’s Try Model-View-Intent With Android
 goo.gl/3fPSWV • Flax For Android - Reactive Concept
 goo.gl/1JjSpN • Hello Flax - A Reactive Architecture For Android
 goo.gl/rD2ZVw
  • 4. The Intent • Interpret user interactions • Input: a view which exposes streams of events • Output: stream of actions
  • 5. The Model • Manage the state of the application • Input: stream of actions • Output: stream of state
  • 6. The View • Visually represent state from the Model • Input: stream of state • Output: stream of events
  • 7. Model Intent MVI Data Flow View User Triggers An Event Intent Emits An Action Model Emits New State View Renders The State
  • 9. class DrawingActivity : AppCompatActivity() { val compositeDisposable = CompositeDisposable() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_drawing) } override fun onResume() { super.onResume() val disposable = DrawingModel(DrawingIntentImpl(drawingView)) .getObservable() .subscribe { drawingView.render(it) } compositeDisposable.add(disposable) } override fun onStop() { compositeDisposable.clear() super.onStop() } } onResume onStop
  • 10. DrawingModel(DrawingIntentImpl(drawingView)) .getObservable() .subscribe { drawingView.render(it) } The Model The Intent The View Called Whenever State Changes Where “it” Is The DrawingState Emits Drawing State Changes
  • 11. interface DrawingIntent { fun getTouches(): Observable<DrawingPoint> fun getColorClicks(): Observable<Boolean> fun getResetClicks(): Observable<Boolean> } Emits DrawingPoint Whenever Touch Event TriggeredEmits Boolean Whenever Color Is ClickedEmits Boolean Whenever Reset Is Clicked
  • 12. class DrawingIntentImpl(private val drawingView: DrawingView): DrawingIntent { override fun getTouches(): Observable<DrawingPoint> { return drawingView.getMotionEvents() .filter { it.action != MotionEvent.ACTION_UP } .map { DrawingPoint(it.x, it.y, it.size, it.eventTime) } } override fun getColorClicks(): Observable<Boolean> = drawingView.getChangeColorClicks().map { true } override fun getResetClicks(): Observable<Boolean> = drawingView.getResetClicks().map { true } } Maps Reset Clicks To Boolean Click Events Come From DrawingView Maps Motion Events To DrawingPoint Only Emit When MotionEvent Is Not ACTION_UP Emits A New DrawingPoint Intent Takes A DrawingView
  • 13. class DrawingModel(drawingIntent: DrawingIntent) { private val randomColor = RandomColor() private val collectedDrawingPoints: ArrayList<DrawingPoint> = ArrayList() private val defaultColor = "#607D8B" private var previousState = DrawingState(emptyList(), defaultColor, false) private val subject: Subject<DrawingState> = ReplaySubject.create() init { Observable.merge( transformTouchesToState(drawingIntent.getTouches()), transformColorClicksToState(drawingIntent.getColorClicks()), transformResetClicksToState(drawingIntent.getResetClicks()) ).subscribe { if (previousState != it) { previousState = it subject.onNext(it) } } } fun getObservable(): Observable<DrawingState> = subject private fun transformColorClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> { // Transforms Color Clicks Into DrawingState } private fun transformResetClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> { // Transforms Reset Clicks Into DrawingState } private fun transformTouchesToState(touches: Observable<DrawingPoint>): Observable<DrawingState> { // Transforms Touch Events Into DrawingState } } Model Takes A DrawingIntent Transform Intent Actions To State Transformations Return DrawingState Handles Emission Of New State Our Activity Subscribes To This
  • 14. data class DrawingState( val drawingPoints: List<DrawingPoint>, val drawingColor: String, val redraw: Boolean ) Collection Of DrawingPoints To Draw What Color Our Line Should Be Whether Or Not We Need To Redraw The Drawing
  • 15. private fun transformColorClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> { return clicks.map { DrawingState(collectedDrawingPoints, randomColor.get(previousState.drawingColor), true) } } private fun transformResetClicksToState(clicks: Observable<Boolean>): Observable<DrawingState> { return clicks.map { collectedDrawingPoints.clear() DrawingState(collectedDrawingPoints, defaultColor, true) } } Emits New Drawing State With Random Color Emits New DrawingState With No DrawingPoints
  • 16. private fun transformTouchesToState(touches: Observable<DrawingPoint>): Observable<DrawingState> { return touches.map { var emitState = previousState collectedDrawingPoints.add(it) if (collectedDrawingPoints.size >= 2) { val currentDrawingPoint = collectedDrawingPoints.get(collectedDrawingPoints.size - 1) val previousDrawingPoint = collectedDrawingPoints.get(collectedDrawingPoints.size - 2) emitState = previousState.copy( drawingPoints = listOf(previousDrawingPoint, currentDrawingPoint), redraw = false ) } emitState } } Mutates Our Previous State To New State Return Our emitState Only Emit New State If More Than Two DrawingPoints Exist
  • 17. interface DrawingView { fun getMotionEvents(): Observable<MotionEvent> fun getChangeColorClicks(): Observable<Any> fun getResetClicks(): Observable<Any> } Emits MotionEvent Whenever User Touches ScreenEmits Anytime The User Clicks Change Color Emits Anytime The User Clicks Reset
  • 18. class DrawingViewImpl(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : LinearLayout(context, attrs, defStyleAttr, defStyleRes), DrawingView { private val paint = Paint() private val strokeWidth = 10f private var cachedBitmap: Bitmap? = null private val changeColorButton: Button private val resetButton: Button override fun getMotionEvents(): Observable<MotionEvent> = RxView.touches(this) override fun getChangeColorClicks(): Observable<Any> = RxView.clicks(changeColorButton) override fun getResetClicks(): Observable<Any> = RxView.clicks(resetButton) fun render(drawingState: DrawingState) { // Renders Our View Based On Drawing State } private fun generateDrawingPath(drawingPoints: List<DrawingPoint>): Path { // Generates The Drawing Path, Called By Render } } Maps To User Interactions Renders Our ViewState Helper Used To Generate Drawing Path
  • 19. fun render(drawingState: DrawingState) { if (!isAttachedToWindow) return if (cachedBitmap == null || drawingState.redraw) { cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) } paint.color = Color.parseColor(drawingState.drawingColor) val drawingCanvas = Canvas(cachedBitmap) drawingCanvas.drawPath(generateDrawingPath(drawingState.drawingPoints), paint) background = BitmapDrawable(resources, cachedBitmap) } New Bitmap When We Need To Redraw Update The Color Of Our Drawing Line Draw Our Line Based On generateDrawingPath
  • 20. private fun generateDrawingPath(drawingPoints: List<DrawingPoint>): Path { val drawingPath = Path() var previousPoint: DrawingPoint? = null for (currentPoint in drawingPoints) { previousPoint?.let { if (currentPoint.time - it.time < 25) { drawingPath.quadTo(it.x, it.y, currentPoint.x, currentPoint.y) } else { drawingPath.moveTo(currentPoint.x, currentPoint.y) } } ?: drawingPath.moveTo(currentPoint.x, currentPoint.y) previousPoint = currentPoint } return drawingPath } Iterates Over The DrawingPoints To Generate A Path