Skip to main content
  1. Blog/

Paging with TootSDK

app programming social Swift Fedicat fediverse Mastodon
Phil Chu
Author
Phil Chu
Making software since the 80s

Whenever you have a feed, you need successive queries to obtain older (and newer) items. The Mastodon API takes and returns descriptors for each page of results, but the return is a link header in the response.

Conveniently, TootSDK conveniently packages the page descriptor in a PageInfo struct that is passed and returned from the TootClient functions that correspond to the API endpoints.

For example, I have a fairly simple timeline display implemented in SwiftUI, with just two state variables, one for the displayed list of latest posts, and a variable holding the page info for the next oldest page to retrieve.

@State private var list: [Post] = []
@State private var prev: PagedInfo?

To get the most recent page of Posts, I call the TootClient query. TootClient.getTimeline takes a timeline type (home, local, federated…) and an optional PageInfo which we leave as null since we want the latest page.

 func getLatest() async {
        do {
            let result = try await toot.getTimeline(timeline)
            list = result.result
            prev = result.previousPage
        } catch {
            // handle error
        }
    }

One we’ve called getLatest, we have our most recent Posts and essentially a pointer the next page, nil if we’re at the end. So we can call this function to to retrieve and append the next page.

func getPrev() async {
        if prev != nil {
            do {
                let result = try await toot.getTimeline(timeline, prev)
                list.append(contentsOf: result.result)
                prev = result.previousPage
            } catch {
               // handle error
            }
        }
    }

Now to display the list

 LazyVStack {
            ForEach(list) { post in
                PostView(post: post).onAppear {
                    if post == list.last {
                        Task {
                            await getPrev()
                        }
                    }
                }
            }
        }
            .onAppear {
                Task {
                    await getLatest()
                }
            }
            .refreshable {
            Task {
                await getLatest()
            }
        }

When this view appears or is pulldown refreshed it gets and displays the latest page. Then as you scroll down (you’ll probably want to wrap a ScrollView around it), once the bottommost item appears, it calls getPrev to fetch the next page.

This is pretty simple, every call to getLatest resets the list and starts from scratch, and if you keep scrolling down the list just grows. You may want to use the TootStream layer around timelines that treats timelines like stream. But there are many other Mastodon/TootClient queries that handle paging in the same way (and not just for posts, also Accounts, Tags…) so this template can be applied to any of those.