Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftUI with TCA in LINE LIVE Commerce Assistant App @ TECHPULSE 2023

SwiftUI with TCA in LINE LIVE Commerce Assistant App @ TECHPULSE 2023

- Speaker: Jason Liang
- Event: http://techpulse.line.me/

隨著電商直播的需求越來越高,因此今年上半年將推出 LINE Live Commerce Assistant App,讓直播主能夠更快速地開啟直播。本次議程將透過 swiftUI 與 TCA,帶大家走訪在 App 開發時所會遇到的各種眉角,了解框架與測試所帶來的好處。

LINE Developers Taiwan

February 21, 2023
Tweet

More Decks by LINE Developers Taiwan

Other Decks in Technology

Transcript

  1. Agenda › LINE Live Commerce Assistant App › High concurrent

    auction chat room › New UI framework and architecture and why › SwiftUI › The Composable Architecture (TCA) › Challenge we met 2
  2. LINE Live Commerce Assistant App Creating a live streaming was

    not that easy Hardware Camera Software Streaming Website CMS 3
  3. KOREA COLOMBIA MEXICO LINE Live Commerce Assistant App Available in

    this year SINGAPORE THAILAND MALAYSIA THE UNITED STATES INDONESIA JAPAN TAIWAN SPAIN TURKEY 4
  4. LINE LIVE Commerce Assistant App 5 An app that allows

    streamers to create and stream at one place
  5. Why SwiftUI Is it really ready for business application? Efficiency

    Less Code Readability Declarative Programming Hot Reload Previews 6
  6. ScrollView { LazyVStack(spacing: 0) { ForEach( viewStore.messages, id: \.id )

    { message in MessageView(message: message) } ... } ... } Better Apps. Less Code 7
  7. Why SwiftUI Readable code and easy to verify UI via

    Xcode Previews struct LiveStopwatchView_Previews: PreviewProvider { private static var startTime = Date() private static var elapsedSeconds: TimeInterval = 31_233 @ViewBuilder static func staticTimeInterval( _ elapsedSeconds: TimeInterval ) -> some View { Text("\(elapsedSeconds, format: .number) seconds") LiveStopwatchView(...) } static var previews: some View { VStack { Grid { GridRow { staticTimeInterval(10_342) } GridRow { staticTimeInterval(359_999) } GridRow { staticTimeInterval(360_000) } } } .padding(40) .previewLayout(.sizeThatFits) } } 8
  8. Why The Composable Architecture (TCA) Building apps in a consistent

    and understandable way, with composition, testing, and ergonomics in mind Independent State SwiftUI Style Dependency Prioritized Testability 9
  9. The Composable Architecture (TCA) The app is composed of state

    of independent features 10 Root Login Home Broadcast Settings
  10. The Composable Architecture (TCA) The app is composed of state

    of independent features 11 Root Login Home Broadcast Settings
  11. ViewStore The Composable Architecture (TCA) The flow of an independent

    composable feature (scope) 12 Reducer State Action StoreOf<Login> LoginView Dependencie
  12. The Composable Architecture (TCA) ReducerProtocol 13 Reducer State Action StoreOf<Login>

    struct Login: ReducerProtocol { struct State: Equatable { var isLoading = false } enum Action { case loginButtonTapped } var body: some ReducerProtocolOf<Self> { Reduce { state, action in switch action { case .loginButtonTapped state.isLoading = true return .task { // login } } } } }
  13. The Composable Architecture (TCA) Dependency Injection 14 Reducer State Action

    StoreOf<Login> struct Login: ReducerProtocol { @Dependency(\.authManager) var authManager enum Action { ... case loginResponse(TaskResult<LoginResult>) } var body: some ReducerProtocolOf<Self> { Reduce { state, action in switch action { case .loginButtonTapped: state.isLoading = true return .task { .loginResponse( TaskResult(await authManager.login()) ) } case .loginResponse(let result): // handle result } } } } Dependencie
  14. The Composable Architecture (TCA) Communication between View and Store 15

    struct LoginView: View { let store: StoreOf<Login> @ObservedObject var viewStore: ViewStoreOf<Login> var body: some View { // ... VStack { Button("Login") { viewStore.send(.loginButtonTapped) } .disabled(viewStore.isLoading) } } } ViewStore Reducer State Action StoreOf<Login> LoginView
  15. The Composable Architecture (TCA) Testing 16 func testLoginButtonTapped() async throws

    { let store = TestStore( initialState: Login.State(isLoading: false), reducer: Login() ) await store.send(.loginButtonTapped, assert: { $0.isLoading = true }) } struct LoginView: View { let store: StoreOf<Login> @ObservedObject var viewStore: ViewStoreOf<Login> var body: some View { // ... VStack { Button("Login") { viewStore.send(.loginButtonTapped) } .disabled(viewStore.isLoading) } } }
  16. The Composable Architecture (TCA) Easy to find the non-matched changes

    from log 17 func testLoginButtonTapped() async throws { let store = TestStore( initialState: Login.State(isLoading: false), reducer: Login() ) await store.send(.loginButtonTapped, assert: { $0.isLoading = true }) } testLoginButtonTapped(): A state change does not match expectation: ... − Login.State(isLoading: false) + Login.State(isLoading: true) (Expected: −, Actual: +)
  17. Challenge We Met It’s really good enough, but we do

    encounter a few challenges › Content offset, if edges get reached and is scrolling › Keyboard presentation/dismissal Scrolling observation Behavior may vary according to iOS version › Extra testing effort Communication between imperative and declarative programming › UI components made for UIKit › e.g. WKWebView 19