I'm trying to figure out how to create a Core Data object from Json data (provided by Financial Modeling Prep).
The application is structured this way:
- An API_SERVICE class with functions like this:
func readJsonIncomeStatementFMP(keywords: String) -> AnyPublisher<[IncomeStatementFMP], Error> {
let result = parseQuery(text: keywords)
var symbol = String()
switch result {
case .success(let query):
symbol = query
case .failure(let error):
return Fail(error: error).eraseToAnyPublisher()
}
let urlString = "https://financialmodelingprep.com/api/v3/income-statement/\(symbol)?apikey=\(API_KEY)"
let urlResult = parseURL(urlString: urlString)
switch urlResult {
case .success(let url):
return URLSession.shared.dataTaskPublisher(for: url)
.map({$0.data})
.decode(type: [IncomeStatementFMP].self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
case .failure(let error):
return Fail(error: error).eraseToAnyPublisher()
}
}
- A SCREENER view in which the API_SERVICE is called:
var body: some View {
@Environment(\.managedObjectContext) private var viewContext
//CoreData
@FetchRequest(entity: Entreprise.entity(), sortDescriptors: [NSSortDescriptor(key: "companyName", ascending: true)]) var entreprises: FetchedResults<Entreprise>
@State var subscribers = Set<AnyCancellable>()
@ObservedObject var apiServiceFMP = APIServiceFMP()
@State var resultScreener: [ResultScreener]?
@State var incomeStatementFMP: [IncomeStatementFMP]?
ZStack{
VStack{
ScrollView(.horizontal){
VStack{
Button(action: {
handleSelectionStockScreenerMultipleProperties(exchange: exchange, country: country, marketCap: marketCap)
}){
Text("Lancer le screen")
.fontWeight(.semibold)
}
.padding()
.foregroundColor(.white)
.background(Color(.green))
.cornerRadius(10)
}
if resultScreener != nil{
VStack{
List(searchResults, id: \.id){ results in
HStack{
Text(results.symbol)
Divider()
Text(results.companyName)
Divider()
Button(action: {
Task{
await handleSelectionIncomeStatementForFMP(for: results.symbol)
await handleSelectionBalanceSheetForFMP(for: results.symbol)
await handleSelectionCashFlowForFMP(for: results.symbol)
await handleSelectionCompanyOutlookForFMP(for: results.symbol)
await handleSelectionHistoricalDailyPriceFMP(for: results.symbol)
}
}){
Text("Infos").foregroundColor(.white)
}.buttonStyle(.plain)
if incomeStatementFMP != nil && balanceSheetFMP != nil && cashFlowFMP != nil && companyOutlookFMP != nil && historicalDailyPriceFMP != nil && incomeStatementFMP!.count > 0 {
if incomeStatementFMP![0].symbol == results.symbol{
Button(action: {
Task{
await handleSelectionIncomeStatementForFMP(for: results.symbol)
await handleSelectionBalanceSheetForFMP(for: results.symbol)
await handleSelectionCashFlowForFMP(for: results.symbol)
await handleSelectionCompanyOutlookForFMP(for: results.symbol)
await handleSelectionHistoricalDailyPriceFMP(for: results.symbol)
}
addEntreprise(incomeStatementFMP: incomeStatementFMP!, balanceSheetFMP: balanceSheetFMP!, cashFlowFMP: cashFlowFMP!, companyOutlookFMP: companyOutlookFMP!, ticker: results.symbol)
}){
Text("Ajout BDD").foregroundColor(.white)
}.buttonStyle(.plain)
.padding()
.background(.blue)
}
}
}
}
}
}
}
}
private func handleSelectionIncomeStatementForFMP(for symbol: String) async{
apiServiceFMP.readJsonIncomeStatementFMP(keywords: symbol).sink { (completionResult) in
switch completionResult {
case .failure(let error):
print(error)
case .finished: break
}
} receiveValue: { (incomeStatementResults) in
self.incomeStatementFMP = incomeStatementResults
}.store(in: &subscribers)
}
private func addEntreprise(incomeStatementFMP: [IncomeStatementFMP], balanceSheetFMP: [BalanceSheetFMP], cashFlowFMP: [CashFlowFMP], companyOutlookFMP: [CompanyOutlookFMP], ticker: String){
if incomeStatementFMP.count > 0 && balanceSheetFMP.count > 0 && cashFlowFMP.count > 0 {
if companyOutlookFMP[0].profile.symbol == ticker && incomeStatementFMP.count > 0 && incomeStatementFMP[0].symbol == ticker && balanceSheetFMP[0].symbol == ticker && cashFlowFMP[0].symbol == ticker{
if entreprises.filter({$0.ticker == ticker}).count != 0 {
for entreprise in entreprises.filter({$0.ticker == ticker}){
if entreprise.incomes!.count == incomeStatementFMP.count {
///Si les données fondamentales n'ont pas changé, on arrête
return
}
else {
///Si les données fondamentales sont plus nombreuses que celles déjà enregistrées
///Il mettre à jour les données de l'objet existant
print("Il faut intégrer la méthode pour modifier l'objet sans le recréer")
}
}
} else {
createNewEntreprise(companyOutlookFMP: companyOutlookFMP, incomeStatementFMP: incomeStatementFMP, balanceSheetFMP: balanceSheetFMP, cashFlowFMP: cashFlowFMP, historicalDailyPriceFMP: historicalDailyPriceFMP!, ticker: ticker)
}
}
} else {
print("Il n'y a pas assez de données, pas d'intérêt d'enregistrer l'entreprise")
}
}
func createNewEntreprise(companyOutlookFMP: [CompanyOutlookFMP], incomeStatementFMP: [IncomeStatementFMP], balanceSheetFMP: [BalanceSheetFMP], cashFlowFMP: [CashFlowFMP], historicalDailyPriceFMP: [HistoricalDailyPriceFMP], ticker: String){
if companyOutlookFMP[0].profile.symbol == ticker && incomeStatementFMP[0].symbol == ticker && balanceSheetFMP[0].symbol == ticker && cashFlowFMP[0].symbol == ticker && historicalDailyPriceFMP[0].symbol == ticker{
let newEntreprise = Entreprise(context: viewContext)
newEntreprise.ticker = companyOutlookFMP[0].profile.symbol
newEntreprise.industry = companyOutlookFMP[0].profile.industry
newEntreprise.sector = companyOutlookFMP[0].profile.sector
newEntreprise.companyName = companyOutlookFMP[0].profile.companyName
newEntreprise.currency = companyOutlookFMP[0].profile.currency
newEntreprise.region = companyOutlookFMP[0].profile.country
newEntreprise.exchange = companyOutlookFMP[0].profile.exchangeShortName
let s = companyOutlookFMP[0].profile.isin
let any : Any? = s
if any != nil {
newEntreprise.isin = companyOutlookFMP[0].profile.isin
} else {
newEntreprise.isin = "Pas de ISIN"
}
newEntreprise.lastPrice = Array(getLastDailyPrice(historicalDailyPrice: historicalDailyPriceFMP)).sorted(by: >)[0].value
newEntreprise.dateLastPrice = Array(getLastDailyPrice(historicalDailyPrice: historicalDailyPriceFMP)).sorted(by: >)[0].key
//Ajouter les données fondamentales
for i in addIncomeToEnterprise(incomeStatementFMP: incomeStatementFMP){
newEntreprise.addToIncomes(i)
//On enregistre les Income Statement
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
for i in addBalanceToEnterprise(balanceSheetFMP: balanceSheetFMP){
newEntreprise.addToBalances(i)
//On enregistre les Balance Sheet
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
for i in addCashToEnterprise(cashFlowFMP: cashFlowFMP){
newEntreprise.addToCashFlows(i)
//On enregistre les Cash Flow
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
//On enregistre l'objet newEntreprise avec toutes les Relationships
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
I think we should use the notion of Async Await.
I would like to load the data with one click on "Ajout BDD".
This is where it doesn't work perfectly -> I have to click on the "Info" button first which calls the API_SERVICE functions before I can press the "Ajout BDD" button which is used to create Core objects Data.
Why when I directly press "Ajout BDD" nothing happens? If I press twice it adds the object in Core Data.
Thanks for your help
I found a solution that needs to be improved but it works. Of the four variables I want to monitor, I added a DidSet :
The rest is handled by the function observeEntreprise :