ATProtoKit icon indicating copy to clipboard operation
ATProtoKit copied to clipboard

[Bug]: Refreshing FeedViewPostDefinition doesn't work

Open ShihabM opened this issue 7 months ago • 1 comments

Summary

Can't refresh FeedViewPostDefinition via the provided method

Reproduction Steps

Image

Calling this function doesn't work.

Expected Results

Updated feed view to be returned.

Actual Results

Getting error: "could not resolve feed did"

What operating systems did you experience this bug?

iOS/iPadOS

Operating System Version

iOS 18

ATProtoKit Version

0.26.6

Additional Context

No response

ShihabM avatar May 21 '25 14:05 ShihabM

I understand a little bit of what's going on with respect to that. I'll have to investigate this to make sure I'm able to replicate this bug, but it may take a bit to do so. Please bare with me here.

MasterJ93 avatar May 22 '25 02:05 MasterJ93

I've only now realized that this issue is completely separate from #146...

This specific bug shouldn't be too difficult to solve.

MasterJ93 avatar May 27 '25 18:05 MasterJ93

I said that this wouldn't be too difficult to solve, but it does seem that it is, indeed, difficult to solve.

Unless I've misunderstood, this refreshing a post from one of the feeds, not the feed itself. I could have sworn I tested all of the methods, but perhaps I tested one of them twice, thinking that this was tested as well.

I'll keep researching, but doing this may not entirely be possible to do, given how the lexicon works.

MasterJ93 avatar May 29 '25 23:05 MasterJ93

After trying various ways, I couldn't resolve this issue. The problem is that it needs to know where in the array to update the post so it can remove the old version and replace it with a new version. I can't seem to find a way to do this in an elegant sort of way. The only way to do this is to ask for an index number.

So something like this:

AppBskyLexicon.Feed.FeedViewPostDefinition.refresh(with: session, from: array, at: index)

Is this alright with you? If not, then I'm not sure how else to resolve this issue.

MasterJ93 avatar Jun 01 '25 10:06 MasterJ93

Thank you for the investigations into this. Yes the above method would work for my use case.

ShihabM avatar Jun 02 '25 13:06 ShihabM

I've dug deeper into this now.

I've looked into the TypeScript code from the official implementation. The "could not resolve feed did" error that's being outputted is because my code is, indeed, giving out the incorrect DID (however, the error code is misleading: it's saying "did" when in actuality, it's asking for the AT URI of the feed).

The current way that the code is written, as well as the proposed solution I've made, makes it impossible to get the AT URI of the feed. So, in order to solve this problem, I need to additionally add a parameter field for the AT URI.

AppBskyLexicon.Feed.FeedViewPostDefinition.refresh(with: session, from: &array, at: index, feedURI: uri)

But then the other problem is that there's the issue of what happens if that post is over the index of 100. If the index is at a very high number (let's say 1,000), then that means 10 of the same API calls, which means you could (potentially) get rate limited. Therefore, I think that the appropriate thing for me to do is to limit this to index 100. If it goes above that limit, then an error will be thrown to state the post is too far past the original index.

Things might change, but this is the solution I have right now.

MasterJ93 avatar Jun 04 '25 15:06 MasterJ93

I've completed it. The final method looks like this:

    public func refresh(
        with atProtoKitConfiguration: ATProtoKit,
        from array: [AppBskyLexicon.Feed.FeedViewPostDefinition],
        at index: Int,
        feedURI: String
    ) async throws -> [AppBskyLexicon.Feed.FeedViewPostDefinition] {
        let post = try await atProtoKitConfiguration.getFeed(by: feedURI).feed[index]

        if index > 99 {
            throw FeedViewPostDefinitionError.indexTooHigh(index: index)
        }

        var newArray = array
        newArray[index] = post
        return newArray
    }

I'm currently testing it, but it will be out in the next update. However, this is a very inelegant solution and I'm unsure if this will be even useful enough for you. But this is the best I can do, given how Bluesky gives out the output of the feed generator's posts.

MasterJ93 avatar Jun 04 '25 16:06 MasterJ93

Thank you for your efforts and for putting this together. I appreciate it. My specific use case was updating posts after liking them or removing likes. Curious to hear whether there's a more obvious way of doing this?

ShihabM avatar Jun 04 '25 17:06 ShihabM

Ah, I understand now..

You could wrap PostViewDefinition in a class and conform it with ObservableObject (since structs and enums can't use ObservableObject), then initialize it with the post view object itself. So something akin to this (this is not 1:1, but you can imagine it would look something like this):

final class PostModel: ObservableObject, Identifiable {
    // Reference to the immutable API struct
    private(set) var post: AppBskyLexicon.Feed.PostViewDefinition

    public let id: UUID
    public let displayName: String
    public let date: Date
    public let text: String
    public let repostCount: Int

    @Published public private(set) var likeCount: Int
    @Published public private(set) var isLiked: Bool

    public init(post: AppBskyLexicon.Feed.PostViewDefinition) throws {
        self.post = post
        self.id = post.id
        self.displayName = post.author.displayName
        self.date = post.date
        self.text = post.record.getRecord(ofType: AppBskyLexicon.Feed.PostRecord.self)?.text
        self.repostCount = post.repostCount
        self.likeCount = post.likeCount
        self.isLiked = post.viewer.likeURI != nil ? true : false
    }
}

And then you can use the .refresh() method in AppBskyLexicon.Feed.PostViewDefinition and add that in an update() method. You can also have a like() and unlike() method:

  • The like() method will create the like record, then increment the number and switch the isLiked property to true (if successful and if no like record exists; you can just check if isLiked is true before creating the record).
  • The unlike() method would check if isLiked is true, and if it does, delete the like record (likeURI would be the one to use), then decrement the number and switch the isLiked property to false.

Note this is for SwiftUI: things are different with UIKit and AppKit, but I don't exactly remember the layout for that.

As for why I'm not making this as a built-in feature, well, it's because it's a lot of work for me to do this and maintain. 😅 It doesn't mean I won't do it: it just means that the bandwidth I have currently won't allow me to do something like this, and I need to seriously think of the common use cases (whether in SwiftUI, AppKit, or UIKit). Perhaps in the future, I will, but for now, this is what I can do to help you at this time.

Hopefully this helps and I apologize if I'm going all over the place: I just want to answer the questions and potential follow up questions that I thought you might have. Still, please let me know if you're confused about anything.

MasterJ93 avatar Jun 04 '25 22:06 MasterJ93