Swift compiler Warning Converting function value of type @MainActor loses global actor MainActor

203 Views Asked by At

I am using Swift UI. I created simple search function to filter the data. OnChnage function I am calling the State property wrapper with view model search function. I done some research that , the data should receive into main thread and that the main reason I have annotated the function with @MainActor but still I got same warning.

Here is the search function code with view model.

@MainActor
final class ProductListViewModel {

    @Published private(set) var productLists: [Product] = []
    @Published private var filteredProductLists: [Product] = []

    private let repository: ProductRepository
    init(repository: ProductRepository) {
        self.repository = repository
    }
}
extension ProductListViewModel: ProductListViewModelAction {
    func getProductList(urlStr: String) async {
            guard let url = URL(string: urlStr) else {
            self.customError = NetworkError.invalidURL
            return
        }
        do {
            let lists = try await repository.getList(for: url)
            productLists = lists.products

        } catch {
          // catching the error 
        }
    }
}

extension ProductListViewModel {
    @MainActor
    func performSearch(keyword: String) {
        filteredProductLists = productLists.filter { product in
            product.title.contains(keyword)
        }
    }
    var productList: [Product] {
        filteredProductLists.isEmpty ? productLists: filteredProductLists
    }
}

Here is my view code ..

    struct ProductListView: View {
    
    @StateObject var viewModel = ProductListViewModel(repository: ProductRepositoryImplementation(networkManager: NetworkManager()))
    @State var searchText = ""

    var body: some View {

        NavigationStack {
            VStack {
                if viewModel.customError != nil && !viewModel.refreshing {
                    alertView()
                } else {
                    if viewModel.refreshing {
                        progressView()
                    }
                    if viewModel.productLists.count > 0 && !viewModel.refreshing {

                        List(viewModel.productList, id: \.self) { product in
                            ProductListViewCell(productData: product)

                        } .listStyle(.grouped)
                    }
                }
            }
            .searchable(text: $searchText)
            .onChange(of: searchText, perform: viewModel.performSearch)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    getToolBarView()
                }
            }
            .navigationTitle(Text("Product List"))
        }.task {
            await getDataFromAPI()
        }
        .refreshable {
            await getDataFromAPI()
        }
    }

    func getDataFromAPI() async {
        await viewModel.getProductList(urlStr: NetworkURL.productUrl)
    }

    @ViewBuilder
    func getToolBarView() -> some View {
        Button {
            Task{
                await getDataFromAPI()
            }
        } label: {
            HStack {
                Image(systemName: "arrow.clockwise")
                    .padding(.all, 10.0)
            }.fixedSize()
        }
        .cornerRadius(5.0)
    }

    @ViewBuilder
    func progressView() -> some View {
        VStack{
            RoundedRectangle(cornerRadius: 15)
                .fill(.white)
                .frame(height: 180)
                .overlay {
                    VStack {
                        ProgressView().padding(50)
                        Text("Please Wait Message").font(.headline)
                    }
                }
        }
    }

    @ViewBuilder
    func alertView() -> some View {
        Text("").alert(isPresented: $viewModel.isErrorOccured) {
            Alert(title: Text("General_Error"), message: Text(viewModel.customError?.localizedDescription ?? ""),dismissButton: .default(Text("Okay")))
        }
    }
}

Error on changes with onChange(of: searchText) { viewModel.performSearch(keyword: searchText) } ..

enter image description here Here is the screenshot of the warning ..

warning

1

There are 1 best solutions below

4
On

onChange is for running an external action, to transform data you should use a computed property into a subview, e.g.

ProductList(products: filteredProducts)
var filteredProducts: [Product] {
    products.filter { product in
        product.title.contains(searchText)
    }
}

Also you need a do/catch inside the .task so you can set an @State to the error. Also you need @State to hold the product lists downloaded. Basically remove the view model object, the View struct already is main actor there is no need for a main actor object and will just slow things down/cause consistency bugs. You would benefit from making repository an EnvironmentKey so you can easily replace it with one containing sample data for previews.