Skip to main content

Attachment

Use the Attachment feature to exchange large amounts — 250 KB or more — of binary data and files between devices and improve peer-to-peer sync performance. Unlike documents, attachments only sync between devices when explicitly fetched. As a result, the large amounts of data that attachments contain do not unnecessarily consume the local disk space attached to the Small Peer and mesh bandwidth.

The Attachment feature uses asynchronous fetch operations so that AttachmentFetcher executes only when the attachment data is available to fetch in the Small Peer or the mesh. To ensure that the attachment fetcher remains a globally available instance and the fetch operation does not silently abort, maintain a strong reference to the attachment fetcher for the entire duration of the asynchronous fetch operation.

Not supported by Swift.

Attachment Feature Use Case

As an example, let’s evaluate how the Ditto Chat demo app for iOS uses the Attachment feature to optionally fetch an avatar image attachment from a collection named 'User'.

For the full code example, see the chat demo app for iOS.

struct User {    var id: String    var firstName: String    var lastName: String                var avatarToken: DittoAttachmentToken?}
let collection = ditto.store["users"]let userID = "1234567"
// add the Alice user to the "users" collectiondo {    try collection.upsert(        [            "_id": userID,            "firstName": "Alice",            "lastName": "Bluebonnet"        ] as [String: Any?]    )} catch {    // handle error    print(error.localizedDescription)    return}

Creating a new Attachment

From the createImageMessage function following the Large Image comment in the chat demo app for iOS code, we first pass the User instance and the URL.path to the avatar image location, and then we create a new DittoAttachment object by calling newAttachment(). Since the Chat demo app does not require an avatar image to be uploaded for each user, we've declared the new attachment as an optional object.

Next, we call the set() function and pass the DittoAttachment object using the DittoMutableDocumentPath instance, which links the User document object ID to the attachment object and stores the attachment’s data bytes and metadata properties to the Small Peer. And then we initialize the Ditto attachment object using DittoAttachmentToken, which we will call later to fetch the attachment data asynchronously from either the Small Peer, if already fetched and available from the local disk, or the peer-to-peer mesh network.

func addAvatar(to user: User, imagePath: String) {    // optionally, add metadata as [String: String]    let metadata = [        "filename": "\(userID)_avatar.png",        "createdOn": ISO8601DateFormatter().string(from: Date())    ]        let attachment = collection.newAttachment(        path: imagePath,        metadata: metadata    )!        // add the attachment to the Alice user document    ditto.store["users"].findByID(user.id).update { mutableDoc in        mutableDoc?["avatarToken"].set(attachment)    }}

Note that on the User model, the avatarToken variable is of type DittoAttachmentToken, yet we call the set() function with a DittoAttachment object. This can be confusing. The set() function, called with the attachment on a DittoMutableDocumentPath instance, causes the data bytes of the attachment at the given file location to be stored in the Ditto database, along with the metadata; the document property is initialized with a DittoAttachmentToken with which we will later fetch the attachment data asnynchronously from the peer-to-peer mesh, or from local storage if it has already been fetched.

Synchronizing the Attachment

Peers can now find the document, fetch the attachment, and use the attachment image. If you want to update a progress view, use the progress event value.

In the following example, we've wrapped DittoCollection.fetchAttachment(token:deliverOn:onFetchEvent:) in an ImageAttachmentFetcher struct for convenience.

struct ImageAttachmentFetcher {    var fetcher: DittoAttachmentFetcher?        init(        ditto: Ditto,        collection: String,        token: DittoAttachmentToken,        onProgress: @escaping (Double) -> Void,        onComplete: @escaping (Result<UIImage, Error>) -> Void    ) {        self.fetcher = ditto.store[collection].fetchAttachment(token: token) { event in            switch event {            case .progress(let downloadedBytes, let totalBytes):                let percent = Double(downloadedBytes) / Double(totalBytes)                onProgress(percent)            case .completed(let attachment):                do {                    let data = try attachment.getData()                    if let uiImage = UIImage(data: data) {                        onComplete(.success(uiImage))                    }                } catch {                    onComplete(.failure(error))                }            default:                print("Error: event case \(event) not handled")            }        }    }}

Notice that we update a progress view by calling the ImageAttachmentFetcher struct with a progress handling closure and a completion handler for handling the fetched image. Since the attachment fetcher must remain a globally available instance for the entire duration of the asynchronous fetch operation, we maintain a strong reference to the attachment fetcher as indicated with a property.

If, at any time, the attachment fetcher goes out of scope, asynchronous fetch operations silently aborts and the Attachment API fails.

// propertiesvar attachmentImage: UIImage?var imageFetcher: ImageAttachmentFetcher?
let doc = collection.findByID(user.id).exec()!let imageToken = doc["avatarToken"].attachmentToken!
imageFetcher = ImageAttachmentFetcher(ditto: ditto, collection: "users", token: imageToken,    onProgress: { percentComplete in        print("Percent complete: \(percentComplete)")    },    onComplete: { result in        switch result {        case .success(let uiImage):            attachmentImage = uiImage        case .failure(let error):            //handle error            print(error.localizedDescription)        }    })

Using with Combine

If you also want to monitor attachment download progress in addition to using fetch operations, instead of AttachedFetcher, use the combined instance FetchAttachmentPublisher.

Note that a FetchAttachmentPublisher is also available. See the API reference for more information.

New and Improved Docs

Ditto has a new documentation site at https://docs.ditto.live. This legacy site is preserved for historical reference, but its content is not guaranteed to be up to date or accurate.