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:
FolioDocumentListStorelists every document in the vault and reports the state of the reception.FolioDocumentStoreshows one document by its id.
Both stores only read. They have no action that shares, edits or deletes a document.
Mount the stores
| Store | Host | Init | State | Action |
|---|---|---|---|---|
FolioDocumentListStore | FolioDocumentListHost | none | FolioDocumentListUiModel | FolioDocumentListAction |
FolioDocumentStore | FolioDocumentHost | FolioDocumentInit | FolioDocumentUiModel | FolioDocumentAction |
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.
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.
| Field | Type | Meaning |
|---|---|---|
id | String | The id of the vault record that holds the document. Pass it to FolioDocumentStore. |
title | String | The document's title. |
documentType | FolioDocumentType | The kind of document: id of the issuer's document template and its localized name. |
issuer | FolioDocumentIssuer | The organization that issued the document: id, name and logoUrl (optional). |
status | FolioDocumentStatus | Whether the document is valid now. See Status. |
isTest | Bool | true for a document issued in a test environment of the issuer. Mark it as a test document in your UI. |
validUntil | String (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
FolioDocumentStatus | Meaning |
|---|---|
Valid | The document is valid now, including a document that expires soon. |
NotYetValid | The document's validity has not started yet. |
Expired | The document's validity has ended. |
Revoked | The 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.
let usable = document.status == .valid && !document.isTestDates
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.
if let photo = document.photos.first {
let image = UIImage(data: try await photo.image.getBytes())
}FolioDocumentListStore
FolioDocumentListUiModel is the state of the list store.
| Field | Type | Meaning |
|---|---|---|
items | [FolioDocument] | Every active document in the vault, sorted by record id. |
reception | FolioDocumentReception | The 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.
| Action | What it does |
|---|---|
Refresh | Reads the vault again, computes status and dates anew, and starts a reception pass. |
Retry | Starts a reception pass. Send it from the retry control of a failed reception. |
Acknowledge | Clears 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
FolioDocumentReception | Meaning |
|---|---|
Idle | Nothing 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. |
Receiving | A 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. |
Failed | The last reception pass failed, for example because the device is offline. text is the localized message and retry the label of its button. |
NotIssued | The issuer declined the issuance or could not issue the document. text is the localized message. There is no retry. |
Received | An 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 } }.
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.
| Field | Type | Meaning |
|---|---|---|
phase | FolioDocumentPhase | Loading, Ready or NotFound. See below. |
document | FolioDocument (optional) | The document while phase is Ready, and the last document after a failed read. |
error | String (optional) | A localized message when the last read failed, such as "Something went wrong" in English. |
FolioDocumentPhase | Meaning |
|---|---|
Loading | The store has not read the document yet. This is also the phase while no session holds a vault and after the session changes. |
Ready | document holds the document. |
NotFound | The 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
RetryandRefreshof 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
receptionstaysIdle, even while Folio still prepares the document. - The status of a mounted store does not change by itself at midnight. See Dates.