Before we go
So... I already had a good time with MVVM, at least it is MVVM in my dimension.
Why’s that?! It because I have worked solo for a quite long time and no under review by anyone so there is no one confirm the way I do, it just me and only me.
But, I confident that I have a good understanding in ReactiveX. At least!
By the way, you can check out how I implemented MVVM through this project.
Now, I begin with how I’m in love with VIPER.
How’s VIPER?
Firstly, I will skip all basic definitions of this architecture and assume you have a well-known about VIPER already and go straight forward to the reason I love VIPER.
1. It’s close. It’s open!
So what is close
/ open
to me?
It means they can stand alone, they only provide you the input and give you the output which is defined by themselves. So that, you can put them anywhere and they still live well.
In VIPERS, I saw these, I saw View
, Interactor
and Route
have it. They provide you input and output, they don’t care about the outside. All they care that when you call an input method they will give what output.
protocol View {
func beginAnimation()
}protocol ViewDelegate {
func animationDidFinish()
}
I prefer naming is View
and ViewDelegate
than ViewInput
and ViewOutput
cause it more nature as Apple did. (TableViewDelegate
, CollectionViewDelegate
)
Plus one, it looks weird if we use ViewInput
and ViewOutput
in this case:
/// InteractorInput & InteractorOutput
protocol InteractorInput { // It's output, not input
var isLoggedIn: Bool { get }
}protocol InteractorOuput { // It's output, but shouldn't leave it in here
// You don't want this usage: `presenter.isLoggedIn` right?
var isLoggedIn: Bool { get }
}/// Interactor & InteractorDelegate
protocol Interactor {
var isLoggedIn: Bool { get }
}
2. Readable to understandable
If someone says that I’m writing code.
NO! I’m writing an essay, I mind about where to add a break line, comment like we care about commas, dot in writing an essay.
So that why readability is quite important to me, I like to read a code file like we read an essay, an newspaper.
Every function and variable come together to make a good paragraph.
And VIPERS can help me to achieve this readable
// Presenter
func viewDidLoad() {
view.beginAnimation()
interactor.fetchUserInformation()
}
As you can see it’s super easy to read and understand what this code block gonna do:
When view did load, view begins animation and interactor fetches user information from server.
MVVM compares to VIPERS
After a while with both, I saw they also have weak and strong, pros and cons, so I try to list everything that in my thought about them
Pros
- Easy to handle complex async tasks by powerful of ReactiveX
// CONTEXT:
//
// The screen have to navigate to Login screen,
// after a cool animation and fetch user information both finish
//
Observable.zip([
aCoolAnimation,
fetchUserInformation
])
.subscribe(onNext: { _ in
navigateToLoginScreen()
})
Cons
- View is mixed up with error handle and navigation logic
// View
viewModel.state
.subscribe(onNext: { state in
switch state {
case .loading:
showLoading()
case .error:
showError() case .done:
navigateToSomewhere()
} })
- Massive view model
- Hard to understand if you don’t have a basic knowledge about ReactiveX
// Tell me! Tell me! What's this code doing?
Observable.merge([
viewModel.localUser,
viewModel.fetchUserFromRemote()
])
.bind(avatarButton.rx.image)
.disposed(by: disposeBag)
The story of VIPER in Flutter (mine)
1. Trouble with UI updates
With this cool stuff from VIPER which I have learned from iOS, so I decided to adapt it into Flutter with the hope I bring happy coding to the Flutter.
In the very first steps, everything works well with Presenter
, Interactor
, and Router
, but not with View
.
When in iOS with UIKit
, we can simply do this to update UI
aLabel.text = "This is text"
But in Flutter, we have to
Widget build(BuildContext context) {
return Column(
children: [
Text("A static content"), // Text 1
Text(content) // Text 2
]
)
}// Call this everywhere to update `Text(content)`
void updateContent(string newContent) {
setState({ content = newContent })
}
Yes, of course, use setState
is a bad idea here cause it will force the whole child widgets to rebuild.
So the smartest choice from my brain that we will use Stream
(aka Observable
) to refresh the smallest number of widgets need to rebuild.
Then, I chose StreamSelector, a convenient of StreamBuilder which wrote by me to do this.
Here is how:
final content = BehaviourSubject<String>.seeded("")Widget build(BuildContext context) {
return Column(
children: [
Text("A static content"), // Text 1
StreamSelector<String>(
stream: content,
builder: (_, content, __) => Text(content) // Text 2
)
]
)
}// Call this everywhere to update `Text(content)`
void updateContent(string newContent) {
content.add(newContent)
}
And we got the final result which just Text 1 is rebuilt
2. Stream in VIPER
So I solved the first barrier, now how to use it into VIPER?
After look up the whole google search results, I found an article that tries to do something like me but for SwiftUI, but they will the same which the way Flutter works.
But one thing I don’t like about this approach that we will have extra ViewModel
to stand between View
and Presenter
. More file, also a new definition in this VIPER. So that, I decided to merge View
and ViewModel
into one which doesn’t provide too much mess and still remain what I love about VIPER
class AView extends StatelessWidget {
// ##### ViewModel - begin #####
final content = BehaviourSubject<String>.seeded("") // Call this everywhere to update `Text(content)`
void setContent(string text) {
content.add(text)
}
// ##### ViewModel - end ##### // ##### View - begin #####
Widget build(BuildContext context) {
return Column(
children: [
Text("A static content"), // Text 1
StreamSelector<String>(
stream: content,
builder: (_, content, __) => Text(content) // Text 2
)
]
)
}
// ##### View - end #####
}
3. Go further with Stream
To take advantage of ReactiveX in handle complex async tasks, I go further in using Stream by convert all input and output functions into streams.
Then, we can use ReactiveX operators to easy to handle such as this case:
abstract class View {
Stream<void> get stateDidInit;
Stream<void> beginAnimation();
}abstract class Interactor {
Stream<UserInfo> fetchUser()
}/// Presenter// When state did init, view begins animation
// and interactor fetchs user
// After both finish, using router to navigate to some where
view.stateDidInit
.flapMap(() => StreamZip([
view.beginAnimation(),
interactor.fetchUser()
])
)
.listen((_) => router.navigateToSomewhere() )
End’s story?
After adopting these ideas into my Flutter IC, I got some.. in fact, a lot of feedback from my reviewers.
So, It’s not the end..