Skip to content

Commit 241905b

Browse files
committed
Merge branch 'main' of github.com:jonnyholland/ComposableArchitecturePattern
2 parents 75d5a4c + 43c57fb commit 241905b

File tree

1 file changed

+25
-5
lines changed

1 file changed

+25
-5
lines changed

README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22

33
This package is designed to demonstrate how to build composable views and code so the views and code can be testable, scalable, and reusable. This also provides a library that's intended to very basic so you don't have to learn the library or use CAP as a framework but rather as a source you can dip into and use when you want or need to. A demos of how to use CAP can be found in [Demo Apps](#demo-apps).
44

5-
Why CAP? Well, since I've been developing apps in SwiftUI for the past 5/6 years, one thing had become very clear to me: there's really not a great way of architecting SwiftUI apps without using older, mostly unrelevant methodologies, such as MVVM. Additionally, I saw bad habits: large observable objects being passed around from view to view or worse yet each view getting an observable object. CAP is designed to fix this by giving general guidelines (protocols) to use to structure and guide your code. See demo apps for examples: [Demo Apps](#demo-apps).
5+
Why CAP? Well, since I've been developing apps in SwiftUI since 2019, one thing had become very clear to me: there's really not a great way of architecting SwiftUI apps without using older, mostly irrelevant methodologies, such as MVVM. Additionally, I saw bad habits: large observable objects being passed around from view to view or worse yet each view getting an observable object when that's not really necessary. CAP is designed to fix this by giving general guidelines (protocols) to use to structure and guide your code. See demo apps for examples: [Demo Apps](#demo-apps).
66

7-
Composable means self-sustained<sup>1</sup>, which means each view should be able to sustain itself. In order to do that the view should have an approach that allows actions in the view to be testable. This means giving the view what it needs so it can be testable, no more no less. This also means architecting our code so we can have a separation of concerns so we're not passing around large view models or objects into each view. There's several ways this can be done and will be discussed below.
7+
Composable means self-sustained<sup>1</sup>, which means each view should be able to sustain itself. In order to do that the view should have an approach that allows actions in the view to be testable. This means giving the view what it needs so it can be testable where objects, values, actions performed in the view can be tested in a straightfoward way. This also means architecting our code so we can have a separation of concerns so we're not passing around large view models or objects into each view. There's several ways this can be done and will be discussed below.
88

99
You'll notice this is called a "pattern". This is because I believe software architecture always needs guidance but not always a library or framework. This approach allows you to make use of the architecture pattern and the library as you see fit. While being light and overall easy to use, writing good code takes time and effort and your goal should be to improve as a developer to architect safe code that hopefully is scalable and reusable.
1010

1111
## Get Started
1212
It would behoove you to read through [Core Principles](#core-principles) to fully understand the overall logic behind this architecture pattern.
1313

14-
# Demo Apps
14+
## Demo Apps
1515
- [Harvard Art Museum](https://github.com/jonnyholland/HarvardArt/tree/main)
1616

1717
## Core Principles
18-
1. Composable. Each object and view should be composable, which means self-contained. So, we should avoid large complex views that are heavily dependent upon another view or on a specific object. There's several ways we can accomplish this:
18+
1. Composable. Each object and view should be composable, which means self-contained. So, we should avoid large complex views that are heavily dependent upon another view or on a specific object. Only give views what they need. It's imperative that your approach towards architecture be one of adapatability. I can't emphasize this enough: **Not all situations are the same. Not all situations require the exact same approach**. I have seen the mistake of forcing the same approach and same template for views in a SwiftUI app more times than I can count and this is a big mistake and, in my opinion, displays a lack of understanding how SwiftUI is designed to operate.
19+
20+
Instead, use the ever popular acronym: KISS (insert clip of Dwight telling Ryan about Michael telling him this and it hurting his feelings) - Rather than the traditional acronym explanation instead use it like this: **K**eep **I**t **S**tupid **S**imple. The goal here is for the architecture and views to be as simple as possible so as complexity grows your work doesn't necessarily grow in complexity.
21+
22+
Therefore, you will need to look at each view from the perspective of "what's the minimal amount that needs to be here?". That's always easier said than done and easier in theory than practice. So, what I usually do when I'm unsure how to architect a view and understand it's needs and whether or not it needs to be reusable, etc: do whatever you need to get the view/feature working, then be a harsh critic and determine how to break down any views or features within the view and how to scope the work so it's clearly understandable by yourself and others later on.
23+
24+
There's several ways we can accomplish this:
1925

2026
a.) Protocols. This is a great way of isolating the view to whatever we define in the protocol so the view can be used anywhere that can conform and provide what the protocol entails.
2127
```swift
@@ -76,7 +82,11 @@ struct ImageViewer<Image: ImageData, Action: ImageAction>: View {
7682
}
7783
```
7884

79-
b.) Predetermined values or models with actions. This is a similar approach to protocols but here we pass in an object or values that aren't specific to any protocol but are specific in what must be used.
85+
b.) Environment. One of the great features of SwiftUI that is done really well is scoping to the environment. You can pass values, objects, etc. into the environment and any child view *within the scope of the view where the values are being passed into the environment* so any view that wants/needs to access the stuff in the environment can by simply adding `@Environment(...) var ...` corresponding to the appropriate environment values.
86+
87+
This can be a great way of reducing the stress on architecting your app/view. This can be as complex or simple as you desire. *Just keep in mind that any view in the hierarchy down stream of where the environment stuff is injected can access the environment values*.
88+
89+
c.) Predetermined values or models with actions. This is a similar approach to protocols but here we pass in an object or values that aren't specific to any protocol but are specific in what must be used.
8090
Here we will use specific values:
8191
```swift
8292
enum ImageAction {
@@ -167,5 +177,15 @@ struct UserCell: View {
167177

168178
As you can see there's parts of this that could get repetitive, such as using class objects for each view.
169179

180+
2. Testability. Make sure your code is testable, actually functionally testable. This requires you to approach objects, views, and overall architure from the perspective of being able to easily test it.
181+
182+
I would like to point out that testing your code is only going to get you so far. In fact, **testability should never come at the cost of stable features/views or eat up loads of development time**. As odd as it may seem, sometimes testability directly impacts quality because the code logic and architecture can become so fragmented that it becomes difficult to work with the code or reliably build out features without heavy/complex overhead or time unit testing simple/basic stuff.
183+
184+
Remember, there's only so much you can test and there's always something you'll miss or the user will expose. The point here is to do your best but don't go overboard. For instance, testing objects returned from a web service is highly valuable and crucial. But, you shouldn't be creating complex objects to handle web service responses, outside of perahps some extreme circumstance. The goal should be to rely on built-in language features to your benefit and make the response objects as straightforward as possible so you don't have to eat up precious development time on tests for a custom decoder/encoder to figure out why it's failing.
185+
186+
If you lay the groundwork for and shape your mind towards testability, you'll find testing can be very easy and fun. Building a unit test will feel more rewarding and less like chess game or figuring out the right pieces to get it to work.
187+
188+
3. Reliability. Architect your code, app, and views so it's reliable. This may seem like a simple thing to point out but it's surprising how often this gets lost in the thought of architecting solutions. This means, again, avoiding massive objects with complex code that's difficult to track things, understand, or scale.
189+
170190
## References
171191
1. (Composability - Wikipedia)[https://en.wikipedia.org/wiki/Composability]

0 commit comments

Comments
 (0)