Folio documents

Show the documents organizations issue to the person, and receive new ones automatically.

Preview

This page describes a pre-release version of the SDK. Names, versions and APIs on this page can change before the release.

An organization can issue a digital document to a person, for example at the end of an inquiry. The document reaches the person's vault as a record with the schema digital. The SDK receives these documents on its own and shows them to your app in one render-ready shape, FolioDocument, through two stores:

  • FolioDocumentListStore lists every document in the vault and reports the state of the reception.
  • FolioDocumentStore shows one document by its id.

Both stores only read. They have no action that shares, edits or deletes a document.

Mount the stores

StoreHostInitStateAction
FolioDocumentListStoreFolioDocumentListHostnoneFolioDocumentListUiModelFolioDocumentListAction
FolioDocumentStoreFolioDocumentHostFolioDocumentInitFolioDocumentUiModelFolioDocumentAction

FolioSdk implements both hosts, and FolioSDKProvider provides them on every platform. Mount the list with FolioDocumentListStore.mount(runtime) and one document with FolioDocumentStore.mount(runtime, init), where FolioDocumentInit has one field, id, the id of a FolioDocument. The stores follow the contract of every store; see Runtime and stores.

On iOS, mount the stores on the runtime and observe them from SwiftUI with @ObservedObject.

Swift
let documents = try FolioDocumentListStore.mount(runtime: sdk)
let document = try FolioDocumentStore.mount(runtime: sdk, init: FolioDocumentInit(id: documentId))

struct DocumentList: View {
    @ObservedObject var store: FolioDocumentListStore

    var body: some View {
        List(store.state.items, id: \.id) { document in
            Text(document.title)
        }
    }
}

FolioDocument

Every text in a FolioDocument is ready to show: labels and values are in the SDK locale, and dates are formatted. Your app renders it as it is.

FieldTypeMeaning
idStringThe id of the vault record that holds the document. Pass it to FolioDocumentStore.
titleStringThe document's title.
documentTypeFolioDocumentTypeThe kind of document: id of the issuer's document template and its localized name.
issuerFolioDocumentIssuerThe organization that issued the document: id, name and logoUrl (optional).
statusFolioDocumentStatusWhether the document is valid now. See Status.
isTestBooltrue for a document issued in a test environment of the issuer. Mark it as a test document in your UI.
validUntilString (optional)The date from which the document is no longer valid, formatted for the SDK locale. Absent when the document does not expire.
fields[FolioDocumentField]The document's text fields in document order, each with a label and a value.
photos[FolioDocumentPhoto]The document's images in document order, each with a label and an image. See Photos.

fields holds the fields the issuer shows on the document. A required field the issuer did not fill in keeps its row with the localized text for a missing value, and an optional field without a value is left out. Every field is shown, including the fields the issuer does not disclose to verifiers. Images are in photos, and attached files are not shown.

documentType.name holds the same text as title: the title the issuer set for the document, or the name of its template when the issuer set none, translated into the SDK language when the issuer provides a translation.

Status

FolioDocumentStatusMeaning
ValidThe document is valid now, including a document that expires soon.
NotYetValidThe document's validity has not started yet.
ExpiredThe document's validity has ended.
RevokedThe issuer revoked the document. This wins over the dates: a revoked document is never Valid.

Revoked follows revokedAt of the record's header, as Folio last reported it. A revoked document stays in the list.

FolioDocumentStatus has no payload. On iOS the cases are .valid, .notYetValid, .expired and .revoked; on Android the objects FolioDocumentStatus.Valid, NotYetValid, Expired and Revoked of a sealed class; on the web the objects { type: 'VALID' }, { type: 'NOT_YET_VALID' }, { type: 'EXPIRED' } and { type: 'REVOKED' }, also available as FolioDocumentStatus.valid and so on.

Swift
let usable = document.status == .valid && !document.isTest

Dates

The SDK computes status from the device's current time and the start of the document's dates in UTC: a document is NotYetValid until 00:00 UTC of its first valid day and Expired from 00:00 UTC of its validUntil date. The order of day and month follows the region of the SDK locale: the regions US, PH, FM, MH and PW put the month first, such as Dec 31, 2026, and every other region puts the day first, such as 31 Dec 2026. When the SDK locale names no region, such as en, the region of the device locale decides. Month names are in the SDK language.

The stores compute the status and the dates each time they read the vault: when they are mounted, when the vault changes, when the SDK locale changes and on Refresh. A store that stays mounted while a day passes keeps the status it computed last, so a document can still show Valid after it has expired. Send Refresh when your screen returns to the foreground.

Photos

image of a FolioDocumentPhoto is an AttachmentRef, a reference to the encrypted image in the vault. The SDK reads and decrypts the bytes only when you ask for them with getBytes(), an asynchronous call. image() on the reference returns an optional ImageMeta with the picture's width, height and file, which carries the file format; it is set for a photo. A reference belongs to the session that listed the document: after a sign-in or sign-out, getBytes() throws the platform's SDK error, and the stores publish new references for the new session.

On iOS, getBytes() returns Data.

Swift
if let photo = document.photos.first {
    let image = UIImage(data: try await photo.image.getBytes())
}

FolioDocumentListStore

FolioDocumentListUiModel is the state of the list store.

FieldTypeMeaning
items[FolioDocument]Every active document in the vault, sorted by record id.
receptionFolioDocumentReceptionThe state of the reception of issued documents. See below.

items holds the active records with the schema digital. A deleted record is not listed. A record the SDK cannot read or project is left out, and the SDK log gets a warning with the target documents and the record's id, so one broken record never hides the others. When the vault cannot be read at all, items keeps the documents it held and the log gets a warning with the target documents; the state carries no error for it. items is empty while no session holds a vault, and it is cleared when the session changes, for example on a sign-in or a sign-out, until the store has read the vault of the new session.

ActionWhat it does
RefreshReads the vault again, computes status and dates anew, and starts a reception pass.
RetryStarts a reception pass. Send it from the retry control of a failed reception.
AcknowledgeClears Received once your app has opened the received document.

On iOS the actions are .refresh, .retry and .acknowledge, on Android FolioDocumentListAction.Refresh, FolioDocumentListAction.Retry and FolioDocumentListAction.Acknowledge, and on the web FolioDocumentListAction.refresh, FolioDocumentListAction.retry and FolioDocumentListAction.acknowledge.

Reception

FolioDocumentReceptionMeaning
IdleNothing is on its way. This is also the state while no session holds a vault, and while the issuer still has to decide about an issuance.
ReceivingA reception pass is running, or a document is on its way: Folio is still preparing it, or the SDK has received it and waits for Folio to confirm.
FailedThe last reception pass failed, for example because the device is offline. text is the localized message and retry the label of its button.
NotIssuedThe issuer declined the issuance or could not issue the document. text is the localized message. There is no retry.
ReceivedAn inquiry on this runtime issued a document and the SDK received it. id is the id of the document in items and of FolioDocumentStore.

Show text with a button labeled retry that sends Retry. In English they read "Couldn’t receive the document" and "Retry". The next pass that succeeds sets the reception back to Idle or Receiving; the SDK also tries again at the next vault sync without a Retry. Refresh and Retry set Receiving at once while their pass runs, even when nothing is on its way, and so does an inquiry that offers an issuance; the pass then sets Idle, Receiving, Failed or NotIssued. A received document appears in items as soon as it is in the vault, with no action of your app.

Show the text of NotIssued with no button. In English it reads "The issuer declined to issue the document" or "The issuer couldn’t issue the document". It stays until the next Refresh or Retry, the next inquiry that offers an issuance, or a change of the session.

Received follows a completed inquiry that issued a document, once the SDK has the document in the vault. Open the document with that id, then send Acknowledge; the reception goes back to Idle. Received stays until then, through later passes that find nothing to receive; Receiving and Failed of a later pass show over it, NotIssued does not. The next inquiry that offers an issuance and a change of the session clear it too. A document that arrives without an inquiry on this runtime, for example after a restart, never sets Received.

On iOS the cases are .idle, .receiving, .failed(text:retry:), .notIssued(text:) and .received(id:); on Android FolioDocumentReception.Idle, FolioDocumentReception.Receiving, FolioDocumentReception.Failed(text, retry), FolioDocumentReception.NotIssued(text) and FolioDocumentReception.Received(id); on the web { type: 'IDLE' }, { type: 'RECEIVING' }, { type: 'FAILED', value: { text, retry } }, { type: 'NOT_ISSUED', value: { text } } and { type: 'RECEIVED', value: { id } }.

Swift
if case let .failed(text, retry) = documents.state.reception {
    ReceptionBanner(text: text, button: retry) {
        try? documents.dispatch(action: .retry)
    }
}

FolioDocumentStore

FolioDocumentUiModel is the state of the store of one document.

FieldTypeMeaning
phaseFolioDocumentPhaseLoading, Ready or NotFound. See below.
documentFolioDocument (optional)The document while phase is Ready, and the last document after a failed read.
errorString (optional)A localized message when the last read failed, such as "Something went wrong" in English.
FolioDocumentPhaseMeaning
LoadingThe store has not read the document yet. This is also the phase while no session holds a vault and after the session changes.
Readydocument holds the document.
NotFoundThe vault has no active record with this id and the schema digital, for example because the record was deleted.

A failed read keeps phase and the last document and sets error; the next read that succeeds clears it. The read failure also goes to the SDK log as a warning with the target documents. Refresh reads the document again; on iOS it is .refresh, on Android FolioDocumentAction.Refresh and on the web FolioDocumentAction.refresh. The phases are .loading, .ready and .notFound on iOS, objects of the sealed class FolioDocumentPhase on Android and { type: 'LOADING' }, { type: 'READY' } and { type: 'NOT_FOUND' } on the web.

Automatic reception

The SDK receives issued documents for every session that holds a vault, a guest's included. Your app sends nothing to start it. The SDK looks for documents to receive:

  • when the session starts,
  • after every successful vault sync,
  • when an inquiry on this runtime completes and issues a document,
  • on Retry and Refresh of the list store,
  • while a document is on its way, on a timer that starts at 3 seconds and doubles up to one minute,
  • once a minute while the issuer still has to decide about an issuance.

An issued document travels as an invite addressed to the person's account. The SDK asks Folio about each pending invite of the account and accepts only the invites that Folio confirms as issued documents. It then confirms the delivery to Folio, so the issuer sees that the person received the document.

The SDK never accepts an ordinary share. An invite another account sends to the person stays pending in incoming of the vault state's sharing view until your app accepts or rejects it.

Limitations

  • The SDK waits for the documents of the current session's identity only. When the identity changes, for example when a guest signs in to an account, it stops waiting for the documents the previous identity's inquiries issued. A document whose invite was addressed to the guest is not received for the account.
  • The SDK keeps the issuances it waits for in memory. After your app restarts, it finds a document once Folio lists its invite for the account; until then reception stays Idle, even while Folio still prepares the document.
  • The status of a mounted store does not change by itself at midnight. See Dates.

On this page