Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS: Login in SwiftUI #12

Merged
merged 1 commit into from
Mar 2, 2022
Merged

iOS: Login in SwiftUI #12

merged 1 commit into from
Mar 2, 2022

Conversation

pchmelar
Copy link
Member

@pchmelar pchmelar commented Feb 25, 2022

📝 Description

  • This PR introduces a new ViewModel concept for SwiftUI views 🚀

❤️ SwiftUI

  • For many years, we used UIKit and the MVVM pattern as a base for views in Presentation Layer. UIKit is a mature UI framework, but it always felt kinda old school compared to the declarative approach used in React, etc. We were partially filling this gap with RxSwift and its bindings.
  • Finally, in 2019 Apple introduced a new UI framework with a declarative approach - SwiftUI. ❤️ Sadly, an iOS 13 was the minimum version for using the SwiftUI. Also, it wasn't that mature in its first releases. Fast forward two and half years, SwiftUI seems ready for production, and most of our projects are already on iOS 13 or higher, so nothing is preventing us from fully adopting SwiftUI. 🍾
  • Although SwiftUI provides a way to handle navigation between views, it has a lot of drawbacks, so the best practice in the community is to keep the navigation UIKit based. Therefore we are keeping our AppDelegate and FlowControllers mostly intact and using SwiftUI where it provides most of its benefits - individual views.
  • As SwiftUI radically changed how we write our views, the big question is what pattern to use with it. 🤔 You can find many articles and comparisons of popular patterns, most of them advising to use some modification of MVVM. However, if you dig deeper, you will notice that there is a new kid on the block - MVI. 👀

💡 MVVM / MVI

  • MVVM is historically one of the most popular patterns for iOS apps, mainly because it abstracts most of the logic out of the view, thus preventing the infamous MassiveViewController. However, one of its drawbacks is that it usually relies on two-way data binding, often resulting in a code that is hard to read and debug. 😔
  • MVI stands for Model - View - Intent. It is a relatively new pattern that goes really well with declarative UI frameworks such as SwiftUI or Jetpack Compose. Key concepts of MVI are unidirectional data flow and immutable state. Everything works in just one direction. When a user interacts with a View, a new Intent is created. The Model responds to it by performing the relevant actions and changing the state. The new state is then reflected on the View. Rinse and repeat. ♻️ See references for more information.
  • We need some container to accept intents, invoke use cases, and update the state. We can adjust our good old friend ViewModel to do exactly that. Instead of Input and Output, we define State and Intent. State is a struct that represents the current state of the view. By using @Published, we ensure that any state changes are propagated to the SwiftUI view. Intent is an enum that represents possible user actions. The whole ViewModel is then injected into the View through its init and referenced as @ObservedObject. Read this article for more information about all those new property wrappers. Also, learn the difference between @ObservedObject and @StateObject. 🤓

Untitled Diagram drawio

🤩 Async / await + @MainActor

  • So now we have a SwiftUI view and a corresponding ViewModel with Intent and State, but how can we connect it with the rest of our architecture? We have a lot of UseCases based on RxSwift, so how can we consume them without a ViewController? Well, we can simply subscribe to them in our ViewModels and update the State when we receive values or errors. But there's a better way! Please welcome async / await. 🎉
  • Async / await comes with Swift 5.5 and immediately gained a lot of attention in the iOS community, but sadly it required iOS 15. Fortunately, this time, Apple came to their senses and made it backward compatible with iOS 13 in Xcode 13.2. It provides a sane way to perform asynchronous tasks in our applications. Read this article for a nice introduction.
  • Most of the asynchronous tasks in our applications consist of simple HTTP requests. There is no need to use streams (i.e. RxSwift or Combine). We mainly used it to avoid callback hell and keep the codebase consistent. Now we can use async / await instead. There are still valid use cases that can benefit from streams, especially when we need real-time updates, but most of the application doesn't need that. BTW AsyncSequence and AsyncStream can also replace RxSwift/Combine in these cases. 👀
  • All of that is nice, but how is it related to our RxSwift based UseCases? It would be nice to ditch the RxSwift and rewrite the whole Domain and Data layers to use only async / await with occasional Combine use when needed, but we can't afford that in most of our projects. 😿 Does it mean that we are stuck with RxSwift in the Presentation layer for many years to come? If we only could somehow consume RxSwift streams using async / await. 🤔 It turns out that RxSwift 6.5.0 brings exactly this functionality! 😌
  • We usually want to execute asynchronous tasks on some background thread, but we must update the UI on the main thread. In RxSwift we ensured that by using Drivers. How can we ensure that when using async / await? @MainActor to the rescue! If you annotate any class with @MainActor it means that all of its properties and methods will automatically be set, called, and accessed on the main queue. It also applies to any of its subclasses. So we can just annotate our BaseViewModel and FlowController classes with @MainActor and it's all done. 🤗 Read this article for a more in-depth explanation.
  • BTW if you ever need to convert RxSwift to Combine and vice versa, there is a bridge named RxCombine.

😶 What’s missing?

  • Proper UI
  • Tests

📚 MVI references

📚 Additional references

@pchmelar pchmelar changed the title WIP: Login in SwiftUI iOS: Login in SwiftUI Feb 25, 2022
@pchmelar
Copy link
Member Author

Tests are failing on the CI because we are still using Xcode 13.1.x there, therefore async-related stuff is limited to iOS 15. We can't update to 13.2.x because of this issue. We can install both 13.1.x / 13.2.x and switch between them, but 13.3 with fix for that issue should be available next week, so I suggest that we just wait for it.

@pchmelar pchmelar merged commit 8c06af3 into develop Mar 2, 2022
@pchmelar pchmelar deleted the feature/login-swift-ui branch March 2, 2022 09:37
@patcesnek patcesnek mentioned this pull request Mar 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants