RxMVVM-Texture best practice
RxSwift MVVM pattern best practice built on Texture(AsyncDisplayKit) and written in Swift
class Repository : Decodable {
var id : Int
var user : User ?
var repositoryName : String ?
var desc : String ?
var isPrivate : Bool = false
var isForked : Bool = false
enum CodingKeys : String , CodingKey {
case id = " id "
case user = " owner "
case repositoryName = " full_name "
case desc = " description "
case isPrivate = " private "
case isForked = " fork "
}
func merge( _ repo: Repository ? ) {
guard let repo = repo else { return }
user? . merge ( repo. user)
repositoryName = repo. repositoryName
desc = repo. desc
isPrivate = repo. isPrivate
isForked = repo. isForked
}
class RepositoryViewModel {
// @INPUT
let didTapUserProfile = PublishRelay < Void > ( )
let updateRepository = PublishRelay < Repository > ( )
let updateUsername = PublishRelay < String ? > ( )
let updateDescription = PublishRelay < String ? > ( )
// @OUTPUT
var openUserProfile : Observable < Void >
var username : Driver < String ? >
var profileURL : Driver < URL ? >
var desc : Driver < String ? >
var status : Driver < String ? >
let id : Int
private let disposeBag = DisposeBag ( )
deinit {
// release Model from DataProvider
RepoProvider . release ( id: id)
}
init( repository: Repository ) {
self . id = repository. id
// retain Model to DataProvider
RepoProvider . addAndUpdate ( repository)
// load Model Observer from ModelProvider
let repoObserver = RepoProvider . observable ( id: id)
. asObservable ( )
. share ( replay: 1 , scope: . whileConnected)
self . username = repoObserver
. map { $0? . user? . username }
. asDriver ( onErrorJustReturn: nil )
self . profileURL = repoObserver
. map { $0? . user? . profileURL }
. asDriver ( onErrorJustReturn: nil )
self . desc = repoObserver
. map { $0? . desc }
. asDriver ( onErrorJustReturn: nil )
class RepositoryListCellNode : ASCellNode {
init ( viewModel: RepositoryViewModel ) {
...
// ViewModel Binding
userProfileNode. rx
. tap ( to: viewModel. didTapUserProfile)
. disposed ( by: disposeBag)
viewModel. profileURL. asObservable ( )
. bind ( to: userProfileNode. rx. url)
. disposed ( by: disposeBag)
viewModel. username. asObservable ( )
. bind ( to: usernameNode. rx. text ( Node . usernameAttributes) ,
setNeedsLayout: self )
. disposed ( by: disposeBag)
viewModel. desc. asObservable ( )
. bind ( to: descriptionNode. rx. text ( Node . descAttributes) ,
setNeedsLayout: self )
. disposed ( by: disposeBag)
viewModel. status. asObservable ( )
. bind ( to: statusNode. rx. text ( Node . statusAttributes) ,
setNeedsLayout: self )
. disposed ( by: disposeBag)
}
class RepositoryListCellNode: ASCellNode {
...
init ( viewModel: RepositoryViewModel ) {
...
// HERE!
userProfileNode. rx
. tap ( to: viewModel. didTapUserProfile)
. disposed ( by: disposeBag)
}
class RepositoryViewModel {
// @INPUT
let didTapUserProfile = PublishRelay < Void > ( )
// @OUTPUT
var openUserProfile : Observable < Void >
...
init ( repository: Repository ) {
...
// HERE!
self . openUserProfile = self . didTapUserProfile. asObservable ( )
}
}
class RepositoryViewController : ASViewController < ASTableNode > {
...
func tableNode( _ tableNode: ASTableNode , nodeBlockForRowAt indexPath: IndexPath )
-> ASCellNodeBlock {
return {
guard self . items. count > indexPath. row else { return ASCellNode ( ) }
let viewModel = self . items [ indexPath. row]
let cellNode = RepositoryListCellNode ( viewModel: viewModel)
// HERE!
viewModel. openUserProfile
. observeOn ( MainScheduler . asyncInstance)
. subscribe ( onNext: { [ weak self] _ in
self ? . openUserProfile ( indexPath: indexPath)
} ) . disposed ( by: self . disposeBag)
return cellNode
}
}
}
class UserProfileViewController: ASViewController < ASDisplayNode > {
...
init ( viewModel: ... ) {
...
// HERE!
self . descriptionNode. textView. rx. text
. bind ( to: self . viewModel. updateDescription,
setNeedsLayout: self . node)
. disposed ( by: self . disposeBag)
}
}
class RepositoryViewModel {
// @INPUT
let updateDescription = PublishRelay < String ? > ( )
// @OUTPUT
var desc : Driver < String ? >
init ( ... ) {
...
let repoObserver = RepoProvider . observable ( id: id)
. asObservable ( )
. share ( replay: 1 , scope: . whileConnected)
self . desc = repoObserver
. map { $0? . desc }
. asDriver ( onErrorJustReturn: nil )
updateDescription. withLatestFrom ( repoObserver) { ( $0, $1) }
. subscribe ( onNext: { text, repo in
guard let repo = repo else { return }
repo. desc = text
RepoProvider . update ( repo)
} ) . disposed ( by: disposeBag)
}
}
Example Video Link