Post

Replies

Boosts

Views

Activity

Reply to Stop using MVVM for SwiftUI
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.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to Stop using MVVM for SwiftUI
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!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’22
Reply to Stop using MVVM for SwiftUI
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:
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jul ’22
Reply to Stop using MVVM for SwiftUI
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             }         }     } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Stop using MVVM for SwiftUI
Model layer class MyWebService {     static let shared = MyWebService()          // URLSession instance / configuration     // Environments (dev, qa, prod, test / mocks)     // Requests and token management          var token: String? = nil          func request<T>(/* path, method, query, payload*/) async throws -> T { ... }          func requestAccess(username: String, password: String) async throws { ... }     func revokeAccess() async throws { ... } } // Other names: User, UserStore, CurrentUser, ... class Account: ObservableObject {     @Published var isLogged: Bool = false     @Published var error: Error? = nil          struct Profile {         let name: String         let avatar: URL?     }          @Published var profile: Profile? = nil          enum SignInError: Error {         case mandatoryEmail         case invalidEmail         case mandatoryPassword         case userNotFound         case userActivationNeeded     }          func signIn(email: String, password: String) async {         // Validation         if email.isEmpty {             error = SignInError.mandatoryEmail             return         } else if email.isEmail {             error = SignInError.invalidEmail             return         }                  if password.isEmpty {             error = SignInError.mandatoryPassword             return         }                  // Submit         do {             try await MyWebService.shared.requestAccess(username: email,                                                         password: password)             isLogged = true             error = nil         } catch HTTPError.forbidden {             error = SignInError.userActivationNeeded         } catch HTTPError.notFound {             error = SignInError.userNotFound         } catch {             self.error = error         }     }          func signOut() async {         // Ignore any error         try? await MyWebService.shared.revokeAccess()         isLogged = false     }          func loadProfile() async {         do {             profile = try await MyWebService.shared.request(...)             error = nil         } catch {             self.error = error         }     } } View layer @main struct MyApp: App {     @StateObject var account = Account()          var body: some Scene {         WindowGroup {             Group {                 if account.isLogged {                     TabView { ... }                         .task {                             async account.loadProfile()                         }                 } else {                     SignInView()                 }             }             .environmentObject(account)         }     } } struct SignInView: View {     @EnvironmentObject var account: Account          @State private var email: String = ""     @State private var password: String = ""          // Text fields, button, error exception     var body: some View {         ScrollView { ... }     }          func signIn() {         Task {             await account.signIn(email: email,                                  password: password)         }     } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Stop using MVVM for SwiftUI
Model layer class MyWebService {     static let shared = MyWebService          // URLSession instance / configuration     // JSONDecoder/Encoder configuration     // Environments (dev, qa, prod, test / mocks)     // Requests and token management          var token: String? = nil          func request<T: Codable>(/* path, method, query, payload*/) async throws -> T { ... }          func requestAccess(username: String, password: String) async throws { ... }     func revokeAccess() async throws { ... } } struct Product: Identifiable, Hashable, Codable {     let id: Int     let name: String     let description: String     let image: URL?     let price: Double     let inStock: Bool } class ProductStore: ObservableObject {     @Published var products: [Product] = []          enum Phase {         case waiting         case success         case failure(Error)     }          @Published var phase: Phase? = nil          func load() async {         do {             phase = .waiting             products = try await MyWebService.shared.request(path: “products”)             phase = .success         } catch {             phase = .failure(error)         }     } } View layer struct ProductList: View {     @StateObject var store = ProductStore()     var body: some View {         List { ... }             .task {                 await store.load()             }             .navigationDestination(for: Product.self) { product in                 ProductView(product: product)                 // (--- OPTIONAL ---)                 // Only if you need to make changes to the store and that store its not already shared                 // ProductView(product: product)                 //    .environmentObject(store)                 // (--- OR ---)                 // ProductView(product: product, store: store)             }     } } struct ProductView: View {     var product: Product     // (--- OR ---)     // If you make changes to the product     // @State var product: Product         // (--- OPTIONAL ---)     // Only if you need to make changes to the store and that store its not already shared     // @EnvironmentObject private var store: ProductStore     // (--- OR ---)     // @ObservedObject var store: ProductStore          var body: some View {         ScrollView { ... }     } }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Stop using MVVM for SwiftUI
KISS (Keep It Simple) vs SOLID in numbers (real world project work hours from my team) Team KISS Development: 120 hours Change #1: 16 hours Change #2: 2 hours Team SOLID Development: +1500 hours Change #1: 48 hours Change #2: 14 hours Everything becomes problematic, complex, unmanageable and expensive. (no other team members understand and want to fix / change) These results are REAL and the company / managers are upset about the SOLID decision. This is very common in many teams and companies. KISS approach is more productive and easy to test, fix, change.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to Stop using MVVM for SwiftUI
Sorry but my personal opinion is only about developers must stop follow SOLID and “Uncle Bob” bad practices. The fact is, in world, SOLID projects (small, big) become complex, expensive and unmanageable, this is a fact, there are results / metrics that prove it. You can see the difference on Apple, after Scott Forstall left everything becomes problematic, buggy and delayed. Scott Forstall was not an Agile / CI / CD / SOLID guy and everything platform (and iOS software) release was perfect! Just works! Every developer and iPhone user see the difference. There’s an iOS before Scott and another after. This is a fact! Also, many don’t know but Agile / “Uncle Bod” approach just killed Microsoft. This is very well known during Windows Longhorn (Vista) development, everything becomes problematic, delayed and we never see some great features. The problems stills next years and they lose the smartphone battle. This is a fact! Note: I think (and hope!) that Apple is not a fully (or obsessed) Agile / CI / CD / SOLID company, but new developer generation could bringing it on.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22
Reply to NavigationStack suggestions
Another thing that could be change is the need for Hashable, why not use the Identifiable like sheet / fullscreenCover?! SwiftUI is turning model object more and more dependent on the view needs. Before SwiftUI: struct Product: Codable { … } After SwiftUI: (we need for ForEach and sheet / fullscreenCover, ok makes sense, any object should have an id) struct Product: Codable, Identifiable { … } After SwiftUI 4.0: (we need for NavigationStack push item) struct Product: Codable, Identifiable, Hashable { … } Bonus: (if we need to use the onChange) struct Product: Codable, Identifiable, Hashable, Equatable { … }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Aug ’22