Showing the contents of a [(String, Double)] in a chart

54 Views Asked by At

I have a list of transactions, each with certain properties but the ones I'm interested in are the dateParsed: String, and a signedAmount: Double, so I can group the transactions by month and created a chart.

I have a TransactionListViewModel with the following code (that I have trimmed in order to try to debug it and make it work):

import Foundation
import Collections

typealias TransactionGroup = OrderedDictionary<String, [Transaction]>
typealias TransactionPrefixSum = [(String, Double)]

@Observable final class TransactionListViewModel {
    var transactions: [Transaction] = []
    
    init() {
        Task {
            transactions = try await getTransactions()
        }
    }
    
    @MainActor
    func getTransactions() async throws -> [Transaction] {
            let data = ... // fetch a JSON from the network with a list of Transaction
            return try JSONDecoder().decode([Transaction].self, from: data)
        } catch {
            print("Error: ", error)
            return []
        }
    }
    
    func accumulateTransactions() async -> TransactionPrefixSum {
        let today = "02/17/2022".dateParsed()
        let dateInterval = Calendar.current.dateInterval(of: .month, for: today)!
        
        var sum: Double = .zero
        var cumulativeSum = TransactionPrefixSum()
        
        return cumulativeSum
    }
}

And I'm trying to call this accumulateTransactions() method in my ContentView:

import SwiftUI
import Charts

struct ContentView: View {
    @Environment(TransactionListViewModel.self) 
    var transactionListViewModel: TransactionListViewModel
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading, spacing: 24) {
                    Text("Overview")
                        .font(.title2)
                        .bold()
                    let data = transactionListViewModel.accumulateTransactions()
                    let totalExpenses = data.last?.1 ?? 0
                    Chart(data, id: \.key) { key, value in
                        LineMark(
                            x: .value(key, value.dateParsed),
                            y: .value(totalExpenses, value.signedAmount)
                        )
                    }
                    RecentTransactionList()
                }
            }
        }
    }
}

However doing this, I just get an error:

The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions and the app can’t be built.

I created a Swift Playground project to test this, and it looks removing the SwiftUI part, it works fine as I see the result when I'm trying to print the content of transactionsViewModel.accumulateTransactions()

I'm not sure what the problem is, perhaps my use of the async/await?

1

There are 1 best solutions below

2
Alexander Volkov On

You have an async call right in the body:

let data = transactionListViewModel.accumulateTransactions()                

It would not compile.

Update as follows:

@available(iOS 16.0, *)
struct ContentView20240117: View {
    @StateObject var transactionListViewModel: TransactionListViewModel
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading, spacing: 24) {
                    Text("Overview")
                        .font(.title2)
                        .bold()
                    if let data = transactionListViewModel.data {
                        let totalExpenses = data.last?.1 ?? 0
                        Chart(data, id: \.0) { key, value in
                            LineMark(
                                x: .value("x", key),
                                y: .value("y", value)
                            )
                        }
                    }
//                    RecentTransactionList()
                }
            }
        }
    }
}

struct Transaction: Decodable {
    let key: String
    let dateParsed: Double
    let signedAmount: Int
}

typealias TransactionGroup = OrderedDictionary<String, [Transaction]>
typealias TransactionPrefixSum = [(String, Double)]

final class TransactionListViewModel: ObservableObject {
    var transactions: [Transaction] = []
    @Published var data: TransactionPrefixSum?

    init() {
        Task {
            transactions = try await getTransactions()
        }
    }

    func updateTotalExpenses() {
        Task {
            let data = await self.accumulateTransactions()
            await MainActor.run {
                self.data = data
            }
        }
    }

    @MainActor
    func getTransactions() async throws -> [Transaction] {
        do {
            let data = "{}".data(using: .utf8)! //... // fetch a JSON from the network with a list of Transaction
            return try JSONDecoder().decode([Transaction].self, from: data)
        } catch {
            print("Error: \(error)")
            return []
        }
    }

    func accumulateTransactions() async -> TransactionPrefixSum {
        let today = Date()
        let dateInterval = Calendar.current.dateInterval(of: .month, for: today)!
        var cumulativeSum = TransactionPrefixSum()
        return cumulativeSum
    }
}