nalexn / clean-architecture-swiftui

SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question: Multiple interactor calls

AndriiTsok opened this issue · comments

We have a page with a non persistent data (we don't use AppState for this one).
It displays two types of information, let's say Product details and Company record.

We are fetching Product data through interactor call and binding its value to loadable binding state by onAppear event.

Once we got the product info data that contains company id - we are pulling the company record. Right now we are doing this simply by handling onAppear event on the UI elements that depends on Product record == .loaded(product).

I am wondering how would handle such a case using Combine. It works but I personally feel that that the code smells.

Would love to hear how would you handle such a case. Seeking to make to more cleaner.

Attaching a simple, minimal example: https://gist.github.com/AndriiTsok/e49a90f0aa648314b6ba0d35ef42fca0

I'd suggest reorganizing the Interactors in this example. There is a clear dependency between loading Product and Company, UI cannot do anything until both are loaded.

The job of the Interactor is not only to be a facade between UI and networking layer, but more importantly, it should provide a convenient API for the UI layer while hiding all the complexity of the business logic.

In terms of the responsibilities, Interactors do resemble classical Services in many use cases, and your example perfectly illustrates where they differ - although OrganizationInteractor and ProductInteractor are good names for entities with "Service" responsibilities, "Interactor" should be more pliable for the UI layer.

You just need to chain two networking requests - querying the product followed by loading the company, and that should look like a single transaction for the UI.

So I would introduce a ProductPageInteractor with a single load function that under the hood chains two networking calls from different Repositories using flatMap. Something like so:

fund load(data: LoadableSubject<(Product, Company)>, productId: String) {
    let cancelBag = CancelBag()
    data.wrappedValue.setIsLoading(cancelBag: cancelBag)
    productWebRepository
        .loadProduct(id: productId)
        .flatMap { [companyWebRepository] product in
              companyWebRepository
                    .loadCompany(id: product.companyId)
                    .map { (product, $0) }
        }
        .sinkToLoadable { data.wrappedValue = $0 }
        .store(in: cancelBag)
}

Thank you @nalexn for your advice! I am closing this one.