Anyone who understands please help me, I want to retrieve data from Firestore and it wants to be read in realtime whenever there is a change from the database, so I use addSnapshotListener to read it, it works every time there is a change from Firestore, but it's still in object form NewsResponse. Because the final result I want to change to a NewsDomainModel form, then I continue the results from NewsResponse to _mapper.transformerResponseToDomain to be converted into NewsDomainModel, but every latest data generated using addSnapshotListener is not updated to the _mapper.transformerResponseToDomain, _mapper only reads 1 time the data sent only.
GetNewsRepository
import SwiftUI
import Core
import Combine
public class GetNewsRepository<
NewsLocaleDataSource: LocaleDataSource,
RemoteDataSource: DataSource,
Transformer: Mapper>: ObservableObject, Repository
where
NewsLocaleDataSource.Request == String,
NewsLocaleDataSource.Response == NewsModuleEntity,
RemoteDataSource.Request == String,
RemoteDataSource.Response == [NewsResponse],
Transformer.Request == String,
Transformer.Response == [NewsResponse],
Transformer.Entity == [NewsModuleEntity],
Transformer.Domain == [NewsDomainModel] {
public typealias Request = String
#Published public var Response: [NewsDomainModel] = [NewsDomainModel]()
private let _localeDataSource: NewsLocaleDataSource
#Published public var _remoteDataSource: RemoteDataSource
#Published public var _mapper: Transformer
public init(
localeDataSource: NewsLocaleDataSource,
remoteDataSource: RemoteDataSource,
mapper: Transformer) {
_localeDataSource = localeDataSource
_remoteDataSource = remoteDataSource
_mapper = mapper
}
public func execute(request: String?) -> AnyPublisher<[NewsDomainModel], Error> {
return self._remoteDataSource.execute(request: request)
.map { self._mapper.transformerResponseToDomain(response: $0) }
.eraseToAnyPublisher()
}
}
GetNewsRemoteDataSource
import Core
import Combine
import FirebaseFirestore
import FirebaseFirestoreSwift
import Foundation
public class GetNewsRemoteDataSource: ObservableObject, DataSource {
public typealias Request = String
#Published public var Response: [NewsResponse] = [NewsResponse]()
private let _endPoint: String
public init(endPoint: String) {
_endPoint = endPoint
}
public func execute(request: String?) -> AnyPublisher<[NewsResponse], Error> {
return Future<[NewsResponse], Error> { completion in
let ref = Firestore.firestore()
ref.collection("news").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("Document not found")
return
}
let dataJson = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: NewsResponse.self)
} // THIS DATA WILL BE AUTOMATIC UPDATE IF DATA FROM FIRESTORE UPDATED
completion(.success(dataJson))
}
}.eraseToAnyPublisher()
}
}
NewsTransformer
import Core
import Combine
public class NewsTransformer<NewsMapper: Mapper>: ObservableObject, Mapper
where
NewsMapper.Request == String,
NewsMapper.Response == NewsResponse,
NewsMapper.Entity == NewsModuleEntity,
NewsMapper.Domain == NewsDomainModel {
#Published public var Request = String()
#Published public var Response = [NewsResponse]()
#Published public var Entity = [NewsModuleEntity]()
#Published public var Domain = [NewsDomainModel]()
private let _newsMapper: NewsMapper
public init(newsMapper: NewsMapper) {
_newsMapper = newsMapper
}
public func transformerResponseToEntity(request: String?, response: [NewsResponse]) -> [NewsModuleEntity] {
return response.map { result in
_newsMapper.transformerResponseToEntity(request: request, response: result)
}
}
public func transformerResponseToDomain(response: [NewsResponse]) -> [NewsDomainModel] {
print("\(response) DATA RESPONSE") // NOT UPDATING
return response.map { results in
_newsMapper.transformerResponseToDomain(response: results)
}
}
public func transformerEntityToDomain(entity: [NewsModuleEntity]) -> [NewsDomainModel] {
return entity.map { result in
_newsMapper.transformerEntityToDomain(entity: result)
}
}
public func transformerDomainToEntities(domain: [NewsDomainModel]) -> [NewsModuleEntity] {
return domain.map { result in
_newsMapper.transformerDomainToEntities(domain: result)
}
}
}
The sequence of images above is GetNewsRepository (Repository), GetNewsRemoteDataSource (Get Data From Firebase), NewsTransformer (Transform from NewsResponse to NewsDomainModel)
Sorry if the title I ask is wrong.
Your Future in GetNewsRemoteDataSource completes. Once a publisher has completed it won't send any further values. Instead of using a Future, return a PassthroughSubject. And use the .send method to pass values through the publisher.
https://developer.apple.com/documentation/combine/passthroughsubject
Related
I've been working on GraphQL for a while and I wanted to use custom publisher and Apollo together. But some kind of custom publisher didn't work, I've been dealing with it for days. I couldn't find what I missed
note: After running fetchPublisher(), no error or data is returned.
Custom Publisher
import Foundation
import Apollo
import Combine
struct ApolloPublisher<Query: GraphQLQuery>: Publisher {
typealias Output = GraphQLResult<Query.Data>
typealias Failure = Error
private let client: ApolloClient
private let query: Query
init(client:ApolloClient,
query: Query) {
self.client = client
self.query = query
}
func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Error, S.Input == GraphQLResult<Query.Data> {
let subscription = ApolloSubscription(client: self.client,
query: self.query,
subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
Custom Subscription
import Foundation
import Apollo
import Combine
final class ApolloSubscription<GraphQuery: GraphQLQuery, SubscriberType: Subscriber>: Subscription where SubscriberType.Input == GraphQLResult<GraphQuery.Data>, SubscriberType.Failure == Error {
private let subscriber: SubscriberType
private var cancelable: Apollo.Cancellable?
init(client: ApolloClient,
query: GraphQuery,
subscriber: SubscriberType) {
self.subscriber = subscriber
self.cancelable = client.fetch(query: query,
resultHandler: self.handle)
}
deinit {
cancelable?.cancel()
}
func request(_ demand: Subscribers.Demand) {}
func cancel() {
cancelable?.cancel()
cancelable = nil
}
func handle(result: Result<GraphQLResult<GraphQuery.Data>, Error>) {
switch result {
case .success(let resultSet):
_ = subscriber.receive(resultSet)
case .failure(let error):
subscriber.receive(completion: .failure(error))
}
subscriber.receive(completion: .finished)
}
}
extension for ApolloClient
extension ApolloClient {
func fetchPublisher<Query: GraphQLQuery>(query: Query) -> ApolloPublisher<Query> {
return ApolloPublisher(client: self,
query: query)
}
}
Client side.
let apollo = ApolloClient(url: URL(string: "https://api.spacex.land/graphql/")!)
func fetch() -> AnyPublisher<String, Error> {
return apollo.fetchPublisher(query: GetLaunchesQuery())
.mapError { error in
return error
}
.map { response in
return response.data?.launches?.description ?? ""
}
.eraseToAnyPublisher()
}
I would like to iterate in my code over the Swift AST like this, finding the struct keyword.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let tokenKind = (child as? TokenSyntax)?.tokenKind, tokenKind == .structKeyword {
// if type(of: child) == StructDeclSyntax.self {
print ("yeah")
}
recursion(node: child)
}
}
let input = """
public struct cmd_deleteEdge<E: VEdge> : EdgeCommand {
public var keyEquivalent = KeyEquivalent.none
public let title = "Delete Edge"
public let id = "deleteEdge"
public let toolTip = "Delete selected Edge"
public let icon = Icon.delete
//receivers
public let edge: E
public init(edge: E) {
self.edge = edge
}
public func execute() throws -> ActionResult {
edge.deleteYourself()
return .success("deleted edge")
}
}
"""
public func convert(structText: String) throws -> String {
let sourceFile = try SyntaxParser.parse(source: structText)
let result = recursion(node: Syntax(sourceFile))
return result
}
try convert(structText: input)
It just simply doesn't work, I never reach the "Yeah" (which means I cannot do anything useful during the recursion).
I find this library very confusing. Would anyone have a good UML diagram explaining how it really works?
Before you tell me, yes I know I could use a Visitor, but I want to understand how it works by myself.
You can use SyntaxProtocol for iterating all items in AST and then use its _syntaxNode public property to make a target syntax, e.g.:
import SwiftSyntax
import SwiftSyntaxParser
func recursion(node: SyntaxProtocol) {
if let decl = StructDeclSyntax(node._syntaxNode) {
print(decl.identifier)
}
node.children.forEach { recursion(node: $0) }
}
let code = """
struct A {}
class Some {
struct B {}
}
func foo() {
struct C {}
}
"""
let sourceFile = try SyntaxParser.parse(source: code)
recursion(node: sourceFile)
Outputs:
A
B
C
NOTE: It is not recommended to retrieve _syntaxNode property directly and you can use Syntax(fromProtocol: node) instead.
SyntaxVisitor
But the best approach is using Visitor pattern with SyntaxVisitor class to avoid recursion issues for large and complex files:
class Visitor: SyntaxVisitor {
var structs = [StructDeclSyntax]()
init(source: String) throws {
super.init()
let sourceFile = try SyntaxParser.parse(source: source)
walk(sourceFile)
}
// MARK: - SyntaxVisitor
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
structs.append(node)
return .skipChildren
}
}
let visitor = try Visitor(source: code)
visitor.structs.forEach {
print($0.identifier)
}
I found it after trial & error and reviewing of the API.
private func recursion(node: Syntax) -> String {
for child in node.children {
if let token = TokenSyntax(child), token.tokenKind == .structKeyword {
print ("yeah")
}
recursion(node: child)
}
return node.description
}
This approach to identify the kind of the token works, and the print statement will be reached. Again, I do wonder how the class diagram for SwiftSyntax would look like.
I'm using CoreStore Wrapper of CoreData with Swift 5
import CoreStore
#objc(Post)
public class Post: NSManagedObject {
}
extension Post {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Post> {
return NSFetchRequest<Post>(entityName: "Post")
}
#NSManaged public var detail: String?
#NSManaged public var sync: Server?
#NSManaged public var time: Time?
}
extension Post {
static var allPosts: [Post] {
var posts : [Post] = []
do {
posts = try CoreStore.fetchAll(From<Post>().tweak({ $0.includesPendingChanges = false }))
} catch {
print(error)
}
return posts
}
}
Fetching all data using.
let posts = Post.allPosts
Getting below error.
⚠️ [CoreStore: Error] From.swift:155
applyToFetchRequest(_:context:applyAffectedStores:) ↪︎ Attempted to
perform a fetch but could not find any persistent store for the entity
(CoreStore.CoreStoreError) .persistentStoreNotFound (
.errorDomain = "com.corestore.error";
.errorCode = 8;
.entity = Post; )
Solve using fetching data into main thread.
DispatchQueue.main.async {
let posts = Post.allPosts
}
The model object is populated using JSONDecoder method. Since it is needed to pass it on tableview i need to initialise a local variable of type KBArticle on UI side. Is there any possible method of initialising an object of the KBArticle without optionals or accessing the inner array directly ?
var kbArticle = KBArticle()
Missing argument for parameter 'from' in call. Insert 'from: <#Decoder#>'
/// UI Side
var kbArticle = KBArticle()
override func viewDidLoad() {
super.viewDidLoad()
/// Web services call method
SDKCore.getInstance.getKbService().fetchKbArticles(with: topicID) { (result) in
switch result {
case .success(let kbArticle):
self.kbArticle = kbArticle
DispatchQueue.main.async {
self.tableView.reloadData()
}
case .failed(let e):
print("Error=\(e)")
}
}
}
// Model Class in SDK
public struct KBArticle: Codable {
public let id: Int
public let topic: String
public let articleCount: Int
public let articles: [Article]
}
public struct Article: Codable {
public let id: Int
public let subject: String
}
/// Method in SDK
public func fetchKbArticles(with topicId: Int, completionHandler: #escaping (ResultModel<KBArticle, Error>) -> Void) {
let request = GetKBArticles(topicId: topicId)
Networking.shared.performRequest(request) { (response) in
switch response {
case .success(let response):
do {
let decoder = JSONDecoder()
let result = try decoder.decode(KBArticle.self, from: response.data!)
completionHandler(.success(result))
} catch let error {
completionHandler(.failed(error))
}
case .failed(let error):
completionHandler(.failed(error))
}
}
}
I have to use this articles inside kbArticle struct to provide my tableview datasource. Is is possible to initialise kbArticle object without a primary initialisation using nil values???
If you need new object without optional , you can set a default value for all params
ex :
public struct KBArticle: Codable {
public var id: Int = 0
public var topic: String = ""
public var articleCount: Int = 0
public var articles: [Article]?
}
public struct Article: Codable {
public var id: Int = 0
public var subject: String = ""
}
Use: let myObj = KBArticle()
Please check this Answer
I am using Vapor 3 to try and create just a sample project where I have a dish, the parent, and the reviews for the dish, the child. All the tutorials that I have been seeing haven't been very clear on how to create the relationship or they are using it in conjecture with leaf. I do not want to use leaf for this, I just want to be able to show all the reviews when for the dish when I give it's id, and it seems that it is different than it was for vapor 2.
My 2 models are Dish and Review
Dish.swift: The parent,
import Foundation
import Vapor
import FluentSQLite
final class Dish: Content {
var id: Int?
var name: String
var course: String
var price: Double
var imageURL: String
var description: String
init(name: String, course: String, price: Double, imageURL: String, description: String) {
self.name = name
self.course = course
self.price = price
self.imageURL = imageURL
self.description = description
}
}
extension Dish {
var reviews: Children<Dish, Review> {
return children(\.dishId)
}
}
extension Dish: Parameter { }
extension Dish: SQLiteModel {
static let entity: String = "Dishes"
}
extension Dish: Migration { }
Review.swift, the child,
import Foundation
import Vapor
import FluentSQLite
final class Review: Content {
var id: Int?
var title: String
var body: String
var dishId: Dish.ID
init(title: String, body: String, dishId: Dish.ID) {
self.title = title
self.body = body
self.dishId = dishId
}
}
extension Review {
var dish: Parent<Review, Dish> {
return parent(\.dishId)
}
}
extension Review: Migration { }
extension Review: SQLiteModel {
static let entity: String = "Reviews"
}
extension Review: Parameter { }
the controller for Dish, DishController,
import Foundation
import Vapor
import FluentSQLite
class DishesController: RouteCollection {
func boot(router: Router) throws {
let dishesRoutes = router.grouped("api/dishes")
dishesRoutes.get("/", use: getAll)
dishesRoutes.get(Dish.parameter, use: getById)
dishesRoutes.post(Dish.self, at: "/", use: createDish)
dishesRoutes.delete(Dish.parameter, use: deleteDish)
}
func deleteDish(req: Request) throws -> Future<Dish> {
return try req.parameters.next(Dish.self).delete(on: req)
}
func createDish(req: Request, dish: Dish) -> Future<Dish> {
return dish.save(on: req)
}
func getAll(req: Request) -> Future<[Dish]> {
return Dish.query(on: req).all()
}
func getById(req: Request) throws -> Future<Dish> {
return try req.parameters.next(Dish.self)
}
}
and the controller for reviews. ReviewController,
import Foundation
import Vapor
import FluentSQLite
class ReviewController: RouteCollection {
func boot(router: Router) throws {
let reviewRoutes = router.grouped("api/reviews")
reviewRoutes.get("/", use: getAll)
reviewRoutes.get(Review.parameter, use: getById)
reviewRoutes.post(Review.self, at: "/", use: createReview)
reviewRoutes.delete(Review.parameter, use: deleteReview)
}
func deleteReview(req: Request) throws -> Future<Review> {
return try req.parameters.next(Review.self).delete(on: req)
}
func createReview(req: Request, review: Review) -> Future<Review> {
return review.save(on: req)
}
func getAll(req: Request) -> Future<[Review]> {
return Review.query(on: req).all()
}
func getById(req: Request) throws -> Future<Review> {
return try req.parameters.next(Review.self)
}
}
this is the routes.swift,
import Vapor
/// Register your application's routes here.
public func routes(_ router: Router) throws {
router.get("/reviews", Dish.parameter,"dish") { request -> Future<Dish> in
return try request.parameters.next(Review.self).flatMap(to: Dish.self) { review in
return review.dish.get(on: request)
}
}
let dishesController = DishesController()
try router.register(collection: dishesController)
let reviewController = ReviewController()
try router.register(collection: reviewController)
}
I just want a simple one to many relationship where one dish can have many reviews, but when I use postman to try and access the reviews for the particular dish, all I get is an error. I know that I used the correct syntax in postman because I can use all the other requests from the controllers just fine, just not any for the relationships. Please tell me what i am missing, because I am getting confused as to what I am doing wrong. If there is anything else I can add please ask.
Thank you very much
If you want to access all reviews for the particular dish, try the following code.
router.get("/dish", Dish.parameter,"reviews") { request -> Future<[Review]> in
return try request.parameters.next(Dish.self).flatMap(to: [Review].self) { (dish) in
return try dish.reviews.query(on: request).all()
}
}
Now In postman, pass a dish id as below:
GET: http://localhost:8080/dish/1/reviews