Stop using MVVM for SwiftUI

Don’t over-engineering! No suggested architecture for SwiftUI, just MVC without the C.

On SwiftUI you get extra (or wrong) work and complexity for no benefits. Don’t fight the system.

For me ... Testing - UI (Compose / SwiftUI) (instrument) & Unit & Integration & End2End & CI/ CD is very important. It is the foundation of Clean Arch.

And you are not thinking long term ??? KMM!
https://kotlinlang.org/lp/mobile/

https://kotlinlang.org/docs/multiplatform-mobile-integrate-in-existing-app.html#what-else-to-share

https://github.com/Kotlin/kmm-integration-sample/blob/master/app/src/main/java/com/jetbrains/simplelogin/androidapp/ui/login/LoginViewModel.kt

~Ash

Sorry, the examples are not testable? Not clean? Today developers are obsessed with tests and other ugly thinks. I see projects delayed with lots problems because devs today want to be SOLID and testable.

Agile, SOLID and over testing works like a cancer for development teams.

Sorry but if people want to be complicated they should move to Android or other platforms.

This is software engineering! KISS (Keep It Simple)

This is SOLID (complicated and obsessed people call this clean…. are you kidding?!)

Remember:

  • Avoid sacrificing software design for testability
  • Automated tests didn’t ensure a quality product

Last years I see a correlation between the increased reliance on automated testing and the decline in quality of software.

A real project that failed and I fixed, one of many problematic projects!

Companies must be careful with devs obsessed with SOLID principles and testability.

In 20 years of software engineering I've never seen soo bad & inefficient developers than in last years. In case of iOS there are very bad developers that come from Web or Android. They fight the system with other platforms concepts and testability talk.

I've seen a lot of questions about MVVM (in general and about testability) unnecessary limitations & problems in WWDC22 SwiftUI digital lounge.

I think developers should start understand & think SwiftUI, instead another platform's strategy.

Apple patterns has been used very successfully by Cocoa developers for decades. It just works, and you don't have to fight it. Jumping into more complicated patterns is not a good approach for developers. Your self, your team, your company and your users will thank you!

I fail to understand how a post like this is being upvotes while it encourages a paradigm SwiftUI itself isn’t based on. Speaking to MVU and Elm patterns is different than identifying “MVC without the C”..

The model is composed by Data Objects (structs), Service Objects (providers, shared classes) and State Objects (observable objects, “life cycle” / “data projection” classes). We should use the state objects for specific (or related) data and functionality, not for screen / view, as they should be independent from specific UI structure / needs, When needed we can share then in view hierarchy.

Remember (using state objects in views):

  • StateObject - strong reference, single source of truth
  • EnvironmentObject / ObservedObject - weak reference

Also (when async calls needed):

  • Define state objects (or base class) as MainActor to avoid warnings and concurrency problems
  • Define state object tasks as async, e.g “func load() async”, because for some situations you need to do other jobs after data load completed and async is more simple / sequencial than checking the “phase” (.loaded) using onChange(of:)

Big apps can have more models. This is just an example:

1/N Thanks for writing this post! I have written many articles regarding SwiftUI and MVVM, I have written books and even created courses on SwiftUI and MVVM. But each time I implemented a solution in SwiftUI and MVVM I felt not comfortable. The main reason was that I was always fighting the framework. When you work on a large application using MVVM with SwiftUI and you want to share state then it becomes extremely hard.

2/N The main reason is that you cannot access EnvironmentObject inside the view model. Well, you can pass it using a constructor but then it becomes pain to do it again and again. And most of the time, you end up getting confused that who is managing the state. I ran into this scenario multiple times and I had to take alternate routes to resolve this problem. One thing to note is that React and Flutter (as original poster said) does not use MVVM pattern. React uses context or Redux and Flutter

3/N Flutter uses bloc, provider etc. But the main idea is the same. All these frameworks works using the uni-directional flow. So, it would be worth it to use EnvironmentObject and just allow EnvironmentObject to be available to all the views. One complain about EnvironmentObject is that it will refresh a lot of views when any value changes. In those cases you can slice up the environment object (https://azamsharp.com/2022/07/01/slicing-environment-object.html).

4/N Although you can use Redux with SwiftUI but Redux have a lot of moving parts. You can simply create a fetchService, which loads the data and make sure to put that fetchService in environmentObject. This allows it to create one single source of truth. Another comment about Model objects. Most of the time, your app in consuming an API. The data you get from the API is not the domain model, that is the DTO (Data Transfer Object). Your domain object lies on the server, most of the time.

This message is from the original poster of this post. If you get this message please contact me on Twitter at @azamsharp. I am writing a post about the same topic and your help will be really appreciated.

Isn't this example of MV fine until you start expanding, for example if you wanted to select a Product in the List and access this state from multiple views, you could put this state on the Product object or would you put the Product you selected on a state object?

Again, you don’t need a middle layer, just your model! Everything becomes easy, composable and flexible. And yes! You can do Unit / Integration tests.

UI = f(State) => View = f(Model)

SignIn

// State (shared in view hierarchy), part of your model
class Account: ObservableObject {
    @Published var isLogged: Bool = false

    func signIn(email: String, password: String) async throws { ... }
}

// View
struct SignInView: View {
    @EnvironmentObject private var account: Account

    @State private var email: String
    @State private var password: String

    // Focus, error state

    var body: some View { ... }

    // Call from button
    func signIn() {
        // Validation (can be on model side)
        // Change error state if needed

        Task {
            do {
                try await account.signIn(email: email,
                                         password: password)
            } catch {
                // Change error state
            }
        }
    }
}

SignUp

// Data (Active Record), part of model, can be an ObservableObject if needed
struct Registration: Codable {
    var name: String = ""
    var email: String = ""
    var password: String = ""
    var carBrand: String? = nil
    
    func signUp() async throws { ... }
}

// State (Store), part of model
class CarBrandStore: ObservableObject {
    @Published var brands: [String] = []
    
    // Phase (loading, loaded, empty, error)

    func load() async { ... }
}

// View
struct SignUpView: View {
    @State private var registration = Registration()
    @StateObject private var carBrandStore = CarBrandStore()
    
    // Focus, error state
    
    var body: some View { ... }

    // Call from button
    func signUp() {
        // Validation (can be on model side)
        // Change error state if needed

        Task {
            do {
                try await registration.signUp()
            } catch {
                // Change error state
            }
        }
    }
}
Stop using MVVM for SwiftUI
 
 
Q