Skip to content

Commit

Permalink
Fix SwiftData crash due to SwiftData missing relationships in certain…
Browse files Browse the repository at this point in the history
… conditions. (#1133)

fix(swiftData): replace relationships calls with direct fetches, due to a SwiftData bug that loses the relationship sometimes
  • Loading branch information
Gio2018 authored Jan 2, 2025
1 parent 780a2ae commit 1d4a9a0
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ struct SlateView: View {
let remoteID: String
let slateTitle: String?
let cards: [HomeCardConfiguration]
let slateInfo: SlateInfo?

@Environment(\.layoutWidth)
private var layoutWidth
Expand All @@ -38,7 +37,7 @@ struct SlateView: View {
private extension SlateView {
func makeHeader(_ title: String) -> some View {
SectionHeader(title: title) {
navigation.navigateTo(SlateDestination(slateID: remoteID, slateTitle: slateTitle, slateInfo: slateInfo))
navigation.navigateTo(SlateDestination(slateID: remoteID, slateTitle: slateTitle))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ struct SharedWithYouDetailView: View {
@Environment(\.horizontalSizeClass)
var horizontalSizeClass

@Environment(\.modelContext)
private var modelContext

var body: some View {
GeometryReader { proxy in
CardCollection(cards: cards, size: .large, layoutWidth: layoutWidth(proxy.size))
Expand All @@ -39,7 +42,7 @@ struct SharedWithYouDetailView: View {
private extension SharedWithYouDetailView {
var proposedCards: [HomeCardConfiguration] {
sharedWithYouItems.enumerated().compactMap {
if let item = $0.element.item {
if let item = fetchItem($0.element.url) {
return HomeCardConfiguration(
givenURL: item.givenURL,
sharedWithYouUrlString: $0.element.url,
Expand All @@ -62,6 +65,19 @@ private extension SharedWithYouDetailView {
return nil
}
}

/// Fetch an `Item` from the underlying `SharedWithYouItem`
/// - Parameter sharedWithYouUrl: `SharedWithYouItem` url
/// - Returns: the item, if it was found
func fetchItem(_ sharedWithYouUrl: String) -> Item? {
let predicate = #Predicate<Item> { $0.sharedWithYouItem?.url == sharedWithYouUrl }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

/// Determine the size of the current layout
/// **NOTE: turns out that, since this is a detail view, the environment value `layoutWidth`
/// cannot be used here since the GeometryReader of HomeView is not active
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct SlateDetailView: View {
@Environment(\.homeActions)
private var homeActions

@Environment(\.modelContext)
private var modelContext

init(destination: SlateDestination) {
self.destination = destination
let slateID = destination.slateID
Expand All @@ -41,7 +44,7 @@ struct SlateDetailView: View {
}
}
.onAppear {
guard let slateInfo = destination.slateInfo else {
guard let slateInfo = slateInfo(destination) else {
return
}
homeActions.trackSlateDetailImpression(info: slateInfo)
Expand All @@ -56,7 +59,7 @@ struct SlateDetailView: View {
private extension SlateDetailView {
var proposedCards: [HomeCardConfiguration] {
recommendations.enumerated().compactMap {
if let item = $0.element.item {
if let item = fetchItem($0.element.remoteID) {
return HomeCardConfiguration(
givenURL: item.givenURL,
sharedWithYouUrlString: nil,
Expand All @@ -79,6 +82,62 @@ private extension SlateDetailView {
return nil
}
}

/// Fetch analytics info for this slate
/// - Parameter destination: slate destination of this slate
/// - Returns: analytics info
func slateInfo(_ destination: SlateDestination) -> SlateInfo? {
guard let slate = fetchSlate(destination.slateID),
let lineup = fetchSlateLineup() else {
return nil
}
return SlateInfo(
slateId: slate.remoteID,
slateRequestId: slate.requestID,
slateExperimentId: slate.experimentID,
slateIndex: Int(slate.sortIndex ?? 0),
slateLineupId: lineup.remoteID
)
}

/// Fetch an `Item` from the underlying `Recommendation`
/// - Parameter recommendationID: `Recommendation` ID
/// - Returns: the item, if it was found
func fetchItem(_ recommendationID: String) -> Item? {
let predicate = #Predicate<Item> { $0.recommendation?.remoteID == recommendationID }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

/// Fetch the current slate from SwiftData
/// - Parameter remoteID: the remote id of this slate
/// - Returns: the slate, if it was found
func fetchSlate(_ remoteID: String) -> Slate? {
let predicate = #Predicate<Slate> { $0.remoteID == remoteID }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

/// Fettch the current slate lineup
/// - Returns: the slate lineup, if it was found
func fetchSlateLineup() -> SlateLineup? {
// there is only one lineup, so we don't need to filter this query
let predicate = #Predicate<SlateLineup> { _ in
return true
}
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

/// Determine the size of the current layout
/// **NOTE: turns out that, since this is a detail view, the environment value `layoutWidth`
/// cannot be used here since the GeometryReader of HomeView is not active
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ protocol NavigationDestination: Codable, Hashable {}
struct SlateDestination: NavigationDestination {
let slateID: String
let slateTitle: String?
let slateInfo: SlateInfo?
}

struct NativeCollectionDestination: NavigationDestination {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ struct RecentSavesView: View {

@State private var cards: [HomeCardConfiguration] = []

@Environment(\.modelContext)
private var modelContext

init() {
let predicate = #Predicate<SavedItem> { $0.isArchived == false && $0.deletedAt == nil }
let sortDescriptor = SortDescriptor<SavedItem>(\.createdAt, order: .reverse)
Expand Down Expand Up @@ -67,7 +70,7 @@ private extension RecentSavesView {

var proposedCards: [HomeCardConfiguration] {
savedItems.enumerated().compactMap {
guard let item = $0.element.item else {
guard let remoteID = $0.element.remoteID, let item = fetchItem(remoteID) else {
return nil
}
return HomeCardConfiguration(
Expand All @@ -91,4 +94,16 @@ private extension RecentSavesView {
)
}
}

/// Fetch an `Item` from the underlying `SavedItem`
/// - Parameter recommendationID: `SavedItem` ID
/// - Returns: the item, if it was found
func fetchItem(_ savedItemID: String) -> Item? {
let predicate = #Predicate<Item> { $0.savedItem?.remoteID == savedItemID }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,15 @@ private extension RecommendationsView {
makeOfflineView()
}
}
private func slateInfo(_ slate: Slate) -> SlateInfo? {
guard let lineup = slate.slateLineup else { return nil }
return SlateInfo(
slateId: slate.remoteID,
slateRequestId: slate.requestID,
slateExperimentId: slate.experimentID,
slateIndex: Int(slate.sortIndex ?? 0),
slateLineupId: lineup.remoteID
)
}

@ViewBuilder
func makeSlatesView() -> some View {
ForEach(slates) {
if let recommendations = $0.recommendations, !recommendations.isEmpty {
SlateView(
remoteID: $0.remoteID,
slateTitle: $0.name,
cards: cards(for: $0.remoteID),
slateInfo: slateInfo($0)
cards: cards(for: $0.remoteID)
)
}
}
Expand All @@ -115,6 +105,9 @@ private extension RecommendationsView {
OfflineView()
}

/// Fetch `Recommendation`s of the current `Slate`
/// - Parameter slateID: `Slate` ID
/// - Returns: the collection of `Recommendation`s, limited to 6 elements.
func fetchRecommendations(_ slateID: String) -> [Recommendation] {
let predicate = #Predicate<Recommendation> { $0.slate?.remoteID == slateID }
let sortDescriptor = SortDescriptor<Recommendation>(\.sortIndex, order: .forward)
Expand All @@ -124,10 +117,22 @@ private extension RecommendationsView {
return (try? modelContext.fetch(fetchDescriptor)) ?? []
}

/// Fetch an `Item` from the underlying `Recommendation`
/// - Parameter recommendationID: `Recommendation` ID
/// - Returns: the item, if it was found
func fetchItem(_ recommendationID: String) -> Item? {
let predicate = #Predicate<Item> { $0.recommendation?.remoteID == recommendationID }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

func cards(for slateID: String) -> [HomeCardConfiguration] {
fetchRecommendations(slateID)
.compactMap {
if let item = $0.item {
if let item = fetchItem($0.remoteID) {
return HomeCardConfiguration(
givenURL: item.givenURL,
sharedWithYouUrlString: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ struct SharedWithYouView: View {

@State private var cards: [HomeCardConfiguration] = []

@Environment(\.modelContext)
private var modelContext

init() {
let sortDescriptor = SortDescriptor<SharedWithYouItem>(\.sortOrder, order: .forward)
var fetchDescriptor = FetchDescriptor<SharedWithYouItem>(sortBy: [sortDescriptor])
Expand Down Expand Up @@ -63,22 +66,35 @@ private extension SharedWithYouView {
.padding(.trailing, 16)
}

/// Fetch an `Item` from the underlying `SharedWithYouItem`
/// - Parameter sharedWithYouUrl: `SharedWithYouItem` url
/// - Returns: the item, if it was found
func fetchItem(_ sharedWithYouUrl: String) -> Item? {
let predicate = #Predicate<Item> { $0.sharedWithYouItem?.url == sharedWithYouUrl }
var fetchDescriptor = FetchDescriptor(predicate: predicate)
fetchDescriptor.fetchLimit = 1

let result = (try? modelContext.fetch(fetchDescriptor)) ?? []
return result.first
}

var proposedCards: [HomeCardConfiguration] {
sharedWithYouItems.enumerated().compactMap {
HomeCardConfiguration(
givenURL: $0.element.item?.givenURL ?? $0.element.url,
guard let item = fetchItem($0.element.url) else { return nil }
return HomeCardConfiguration(
givenURL: item.givenURL,
sharedWithYouUrlString: $0.element.url,
type: .sharedWithYou,
index: $0.offset,
shareURL: $0.element.item?.shareURL,
domain: $0.element.item?.bestDomain,
timeToRead: $0.element.item?.timeToRead,
isSyndicated: $0.element.item?.isSyndicated == true,
recommendationID: $0.element.item?.recommendation?.analyticsID,
bestTitle: $0.element.item?.bestTitle,
slug: $0.element.item?.collectionSlug,
excerpt: $0.element.item?.excerpt,
topImageURL: $0.element.item?.topImageURL,
shareURL: item.shareURL,
domain: item.bestDomain,
timeToRead: item.timeToRead,
isSyndicated: item.isSyndicated == true,
recommendationID: item.recommendation?.analyticsID,
bestTitle: item.bestTitle,
slug: item.collectionSlug,
excerpt: item.excerpt,
topImageURL: item.topImageURL,
enableSaveAction: true,
enableShareMenuAction: true,
enableReportMenuAction: true
Expand Down

0 comments on commit 1d4a9a0

Please sign in to comment.