Skip to content

Attachment

The attachment allows you to select files or capture images and video, enabling the application to access them for post-processing, such as uploading to a server.

The attachment has two modes of operation: edit mode and view mode.

Edit mode allows you to remove existing attachments or add new attachments to the attachment. Existing attachments are displayed in a list view and different UI elements, such as Add and Remove icon buttons, are provided for updating the attachments. Tapping the Add button shows a variety of preconfigured methods for adding files.

Attachment Attachment

In view mode (also known as "non-edit" mode), you can only browse through the existing attachments and cannot add or remove attachments from the attachment. View mode provides the option to display attachments in either list or grid view. It also allows you to open the files using different applications.

Attachment Attachment

Using the Attachment

The attachment can be used in your app like any traditional composable function:

var editable by rememberSaveable { mutableStateOf(false) }
val actions = mutableListOf<FioriAttachmentAction>()

actions.add(FioriAttachmentActionSelectPicture(processor = singleFilePickActionProcessor))
actions.add(
    FioriAttachmentActionSelectFile(
        processor = FioriAttachmentActionProcessorBuilder.buildSelectMultiFilesActionProcessor()
    )
)
actions.add(
    FioriAttachmentActionSelectDocuments(
        processor = singleFilePickActionProcessor,
        appendDivider = true
    )
)
actions.add(
    FioriAttachmentActionTakeVideo(
        processor = FioriAttachmentActionProcessorBuilder.buildCaptureVideoActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir("Videos") ?: context.cacheDir).path.plus("/VID_${
                            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))}.mp4"
                        )
                    )
                )
            }
        )
    )
)
actions.add(
    FioriAttachmentActionTakePicture(
        processor = FioriAttachmentCapturePictureActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir(
                            Environment.DIRECTORY_PICTURES
                         ) ?: context.cacheDir).path.plus("/JPEG_${LocalDateTime.now()
                                 .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))
                        }.jpg"
                    )
                )
                )
            },
            operator = defaultFioriAttachmentActionCapturePictureOperator
        ))
)

// Sample to use saveable list to keep the attachments
val attachments = rememberSaveableFioriAttachmentItemList(attachmentItems = mutableListOf())
FioriAttachment(
    editable = editable,
    actions = actions,
    attachments = attachments,
    onAttachmentsAdd = { items ->
        attachments.addAll(items)
    },
    onAttachmentItemDelete = {
        attachments.remove(it)
    },
)

// Sample code to use ViewModel to keep all attachments
val viewModel = viewModel<FioriAttachmentViewModel>(
        factory = FioriAttachmentViewModelFactory(
            actionRepository = DefaultFioriAttachmentActionRepository(
                actions.toMutableList()
            ),
        attachmentRepository = DefaultFioriAttachmentItemRepository(
            rememberSaveableFioriAttachmentItemList(attachmentItems = listOf())
        )
    )
)

FioriAttachment(
    editable = editable,
    viewModel = viewModel,
    onAttachmentsAdd = { items ->
        viewModel.addAttachments(items)
    },
    onAttachmentItemDelete = { _, item ->
        viewModel.removeAttachment(item)
    }
)

Listening To Changes

The attachment supports five callbacks, onAttachmentsChange, onAttachmentsAdd, onAttachmentItemDelete, onAttachmentItemClick and onAttachmentItemLongClick, to notify the application about updates in the attachments.

  • The onAttachmentsChange callback notifies you when a file resource was added or removed from the attachment. You don't need to maintain the list of FioriAttachmentItem in the callback. This callback will be invoked when maintaining additions and deletions of attachments in the corresponding onAttachmentsAddand onAttachmentItemDelete callback.
  • The onAttachmentsAdd callback notifies you when an attached file resource is added. You should maintain a mutable list of FioriAttachmentItem in this callback to keep track of added attachments.
  • The onAttachmentItemDelete callback notifies you when a file resource is removed from the attachment. Similarly to the onAttachmentsAdd callback, you should maintain a mutable list of FioriAttachmentItem within this callback to track deletions of attachments.
  • The onAttachmentItemClick callback notifies you when an attached file resource has been clicked. By default, the attachment will open the file resource using a suitable third-party application. However, you can override the behavior to return false in the callback if, for example, for security, you don't want to open the attached file resource. Then, you can return false in the callback.
  • The onAttachmentItemLongClick callback notifies you when an attached file resource has been long-clicked.
var editable by rememberSaveable { mutableStateOf(false) }
val context = LocalContext.current

val multiFileProcessor = FioriAttachmentActionProcessorBuilder.buildSelectMultiFilesActionProcessor()
val actions = listOf(
     FioriAttachmentActionSelectFile(
        processor = multiFileProcessor
    )
)

// Sample code to use saveble mutable-list to keep all attachments
val attachments = rememberSaveableFioriAttachmentItemList(attachmentItems = listOf())
FioriAttachment(
    editable = editable,
    actions = actions,
    attachments = attachments,
    onAttachmentsChange = {},
    onAttachmentsAdd = { items ->
        attachments.addAll(items)
    },
    onAttachmentItemDelete = {
        attachments.remove(it)
    },
    onAttachmentItemClick = { _, item ->
        Toast.makeText(
            context,
            "Opening file '${item.getFileName(context)}'", Toast.LENGTH_SHORT).show()
        true
    },
    onAttachmentItemLongClick = { _, item ->
        Toast.makeText(
            context,
            "Long Clicking file '${item.getFileName(context)}'", Toast.LENGTH_SHORT).show()
    }
)

// Sample code to use ViewModel to keep all attachments
val viewModel = viewModel<FioriAttachmentViewModel>(
        factory = FioriAttachmentViewModelFactory(
            actionRepository = DefaultFioriAttachmentActionRepository(
                actions.toMutableList()
            ),
        attachmentRepository = DefaultFioriAttachmentItemRepository(
            rememberSaveableFioriAttachmentItemList(attachmentItems = listOf())
        )
    )
)

FioriAttachment(
    editable = editable,
    viewModel = viewModel,
    onAttachmentsChange = {},
    onAttachmentsAdd = { items ->
        viewModel.addAttachments(items)
    },
    onAttachmentItemDelete = { _, item ->
        viewModel.removeAttachment(item)
    },
    onAttachmentItemClick = { _, item ->
        Toast.makeText(
            context,
            "Opening file '${item.getFileName(context)}'", Toast.LENGTH_SHORT).show()
        true
    },
    onAttachmentItemLongClick = { _, item ->
        Toast.makeText(
            context,
            "Long Clicking file '${item.getFileName(context)}'", Toast.LENGTH_SHORT).show()
    }
)

Attachment Action

In order to attach files to the attachment, you should be able to:

  • Pick a variety of files, such as PDFs, documents, images, or zip files
  • Pick the files from a variety of sources, such as Google Photos, Google Drive, Dropbox, or local storage
  • Pick files using different mechanisms, such as an attachment from the Camera or storage

As an app developer you may want to configure the attachment to allow only specific types of attachments. To fulfill these kinds of requirements, attachment depends on the FioriAttachmentAction class.

FioriAttachmentAction defines the type of file and method to pick those files.

For example, FioriAttachmentActionTakeVideo allows you to capture video using the camera and attach that to the attachment. Similarly FioriAttachmentActionSelectDocuments allows you to pick PDF documents. There are multiple different predefined AttachmentActions provided with the library:

Attachment Action Description
FioriAttachmentActionTakeVideo Opens the camera and allows the user to capture video and attach it to the attachment
FioriAttachmentActionTakePicture Opens the camera and allows the user to capture an image and attach it to the attachment
FioriAttachmentActionSelectVideo Allows the user to select video from different sources and attach it to the attachment
FioriAttachmentActionSelectPicture Allows the user to select pictures from different sources and attach them to the attachment
FioriAttachmentActionSelectMedia Allows the user to select media files (images, videos, and audio files) from different sources and attach them to the attachment
FioriAttachmentActionSelectDocuments Allows the user to select PDF files from different sources and attach them to the attachment
FioriAttachmentActionSelectFile Allows the user to select any file format from different sources and attach it to the attachment

FioriAttachmentActionProcessor

The FioriAttachmentActionProcessor is mandatory for every FioriAttachmentAction. This open class was designed to encapsulate the logic for selecting the file resources to be attached. There are four predefined FioriAttachmentActionProcessor classes: FioriAttachmentSelectSingleFileActionProcessor, FioriAttachmentSelectMultiFilesActionProcessor, FioriAttachmentCapturePictureActionProcessor, and FioriAttachmentCaptureVideoActionProcessor. However, you have the option to either implement a concrete class of FioriAttachmentActionProcessor and encapsulate your own logic within the runAction method, or initialize a FioriAttachmentActionProcessor with the operator parameter. This parameter should be a composable callback function that implements your specific logic, especially if your logic can only run within a composable function.

  • FioriAttachmentSelectSingleFileActionProcessor allows you to select one single file resource and should be used by predefined FioriAttachmentActionSelectVideo, FioriAttachmentActionSelectPicture, FioriAttachmentActionSelectMedia, FioriAttachmentActionSelectDocuments, and FioriAttachmentActionSelectFile.
  • FioriAttachmentSelectMultiFilesActionProcessor allows you to select multiple file resources and should be used by predefined FioriAttachmentActionSelectVideo, FioriAttachmentActionSelectPicture, FioriAttachmentActionSelectMedia, FioriAttachmentActionSelectDocuments, and FioriAttachmentActionSelectFile.
  • FioriAttachmentCapturePictureActionProcessor allows you to take a picture and should be used by FioriAttachmentActionTakePicture.
  • FioriAttachmentCaptureVideoActionProcessor allows you to take a video and should be used by FioriAttachmentActionTakeVideo.

FioriAttachmentActionTakePicture

FioriAttachmentActionTakePicture is a special Attachment action. It allows you to capture an image and attach it to the attachment. You can find the details about how to capture an image using the camera here.

In a very simplified version, to capture an image you need to trigger an implicit intent using an Intent action MediaStore.ACTION_IMAGE_CAPTURE. The triggered Intent opens the Camera activity, which saves the captured image to local storage and returns the control back to the calling Activity.

In order to store the captured image to the local storage, the application requires access to the specific location in local storage. You can provide access by creating a FileProvider. Each FileProvider exposes a specific location and an authority string.

Apps that have access to the authority string can access the location associated with the FileProvider.

FioriAttachmentActionTakePicture is designed to hide these complexities in capturing images and simplifies the process of attaching the captured image to the attachment. However, in order to store the captured image, the attachment also needs access to storage. In other words, it requires the authority of the FileProvider. See How to create a FileProvider for more information.

Below is an example of how to declare a FileProvider that provides access to the Pictures and Videos directory and exposes an authority, com.sap.cloud.mobile.fiori.horizon.composedemo.attachment.provider.

In the AndroidManifest.xml file:

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.sap.cloud.mobile.fiori.horizon.composedemo.attachment.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/demo_capture_path"/>
    </provider>

Resource file: demo_capture_path, used in the FileProvider declaration above.

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path
            name="fiori_attachment_capture_image_file_path"
            path="Android/data/com.sap.cloud.mobile.fiori.horizon.composedemo/files/Pictures" />
        <external-path
            name="fiori_attachment_capture_video_file_path"
            path="Android/data/com.sap.cloud.mobile.fiori.horizon.composedemo/files/Videos" />
    </paths>

Once you have correctly configured the FileProvider, you can instantiate the FioriAttachmentActionTakePicture object using the FioriAttachmentCapturePictureActionProcessor object.

To create a FioriAttachmentCapturePictureActionProcessor object, you can initialize it with a composable callback that returns a file path where the captured picture is stored, and defaultFioriAttachmentActionCapturePictureOperator as the operator. Alternatively, you can use the FioriAttachmentActionProcessorBuilder object, which offers the buildCapturePictureActionProcessor method. See the following code snippet:

    FioriAttachmentActionTakePicture(
        // Get the FioriAttachmentCapturePictureActionProcessor via the FioriAttachmentActionProcessorBuilder.buildCapturePictureActionProcessor method
        processor = FioriAttachmentActionProcessorBuilder.buildCapturePictureActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir(
                            Environment.DIRECTORY_PICTURES
                         ) ?: context.cacheDir).path.plus("/JPEG_${LocalDateTime.now()
                                 .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))
                        }.jpg"
                    )
                ))
            }
        )
    )

    FioriAttachmentActionTakePicture(
        // Initialize the FioriAttachmentCapturePictureActionProcessor with defaultFioriAttachmentActionCapturePictureOperator
        processor = FioriAttachmentCapturePictureActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir(
                            Environment.DIRECTORY_PICTURES
                         ) ?: context.cacheDir).path.plus("/JPEG_${LocalDateTime.now()
                                 .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))
                        }.jpg"
                    )
                ))
            },
            operator = defaultFioriAttachmentActionCapturePictureOperator
        )
    )

FioriAttachmentActionTakeVideo

Similar to FioriAttachmentActionTakePicture, FioriAttachmentActionTakeVideo allows you to capture a video and attach it to the attachment. The FileProvider is required here for the FioriAttachmentActionTakeVideo. You can initialize it with a composable callback that returns a file path where the captured picture is stored, and defaultFioriAttachmentActionCaptureVideoOperator as the operator. Alternatively, you can use the FioriAttachmentActionProcessorBuilder.buildCaptureVideoActionProcessor method to create the FioriAttachmentCaptureVideoActionProcessor object for FioriAttachmentActionTakeVideo. See the following code snippet:

    FioriAttachmentActionTakeVideo(
        // Get the FioriAttachmentCaptureVideoActionProcessor via the FioriAttachmentActionProcessorBuilder.buildCaptureVideoActionProcessor method
        processor = FioriAttachmentActionProcessorBuilder.buildCaptureVideoActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir("Videos") ?: context.cacheDir).path.plus("/VID_${
                            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))}.mp4"
                        )
                    )
                )
            }
        )
    )

    FioriAttachmentActionTakeVideo(
        // Initialize the FioriAttachmentCaptureVideoActionProcessor with defaultFioriAttachmentActionCaptureVideoOperator
        processor = FioriAttachmentCaptureVideoActionProcessor(
            uri = {
                FileProvider.getUriForFile(
                    context,
                    LocalContext.current.packageName + ".attachment.provider",
                    File(
                        (context.getExternalFilesDir("Videos") ?: context.cacheDir).path.plus("/VID_${
                            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"))}.mp4"
                        )
                    )
                )
            },
            operator = defaultFioriAttachmentActionCaptureVideoOperator
        )
    )

FioriAttachmentProgrammaticAction

The FioriAttachmentProgrammaticAction allows you to set attachments programmatically. You can either implement a concrete class of FioriAttachmentActionProcessor and encapsulate your logic within the runAction method, or you can initialize a FioriAttachmentActionProcessor with the operator parameter. This parameter should be a composable callback function that implements your specific logic. See the following code snippet:

private fun lookupCapturedMediaFiles(context: Context, action: FioriAttachmentAction) {
    val directory = context.getExternalFilesDir("/")

        directory?.let {
            val files = directory.listFiles()
            files?.let {
                val attachments = mutableListOf<FioriAttachmentItem>()
                for (file in files) {
                    if (file.isDirectory && file.endsWith(Environment.DIRECTORY_PICTURES)) {
                        val pictures = file.listFiles()
                        pictures?.let {
                            for (pic in pictures) {
                                val uri = FileProvider.getUriForFile(
                                    context,
                                    context.packageName + ".attachment.provider",
                                    pic,
                                )
                                attachments.add(FioriAttachmentItem(uri))
                            }
                        }
                    } else if (file.isDirectory && file.endsWith("Videos")) {
                        val videos = file.listFiles()
                        videos?.let {
                            for (video in videos) {
                                val uri = FileProvider.getUriForFile(
                                    context,
                                    context.packageName + ".attachment.provider",
                                    video,
                                )
                                attachments.add(FioriAttachmentItem(uri))
                            }
                        }
                    }
                }
                if (attachments.isNotEmpty()) resultHandler.onResultAttach(attachments)
            }
        }
}

class DemoAttachmentProgrammaticActionProcessor : FioriAttachmentActionProcessor() {
    override fun runAction(context: Context, action: FioriAttachmentAction) {
        lookupCapturedMediaFiles(context = context, action = action)
    }
}

// initialize FioriAttachmentProgrammaticAction with concrete class of FioriAttachmentActionProcessor
FioriAttachmentProgrammaticAction(
    processor = DemoAttachmentProgrammaticActionProcessor(),
    requiredPermissions = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
        listOf(
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
        else
            listOf()
)

// initialize FioriAttachmentProgrammaticAction with an instance of FioriAttachmentActionProcessor
FioriAttachmentProgrammaticAction(
    processor = FioriAttachmentActionProcessor(
                    operator = { action, finish ->
                        val context = LocalContext.current
                        lookupCapturedMediaFiles(context = context, action = action)
                        finish()
                    },
    ),
    requiredPermissions = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
        listOf(
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
        else
            listOf()
)

To demonstrate this feature, we implement a concrete class, DemoAttachmentProgrammaticActionProcessor, of FioriAttachmentActionProcessor. It will select all image and video files from the path declared in the preceding demo_capture_path.xml and attach them to the attachment using the resultHandler.onResultAttach(attachments) method. We also assume that the application has read permission for Manifest.permission.READ_EXTERNAL_STORAGE. The permission is set using the requiredPermissions parameter of the FioriAttachmentProgrammaticAction.

Customization

The attachment offers several methods for customization using FioriAttachmentDefaults. You can use it to customize the attachment's colors, text styles, height and width, etc.

Action

Also, you may want to customize the predefined FioriAttachmentAction. If, for example, you want the FioriAttachmentActionSelectMedia to work with audio, image, and video files, you can set the mimeType parameter as arrayOf("image/*", "video/*", "audio/*") for the FioriAttachmentActionSelectMedia. You may also customize the icon and label for FioriAttachmentActionSelectMedia See the following code snippet:

FioriAttachmentActionSelectMedia(
    processor = FioriAttachmentActionProcessorBuilder.buildSelectMultiFilesActionProcessor(),
    icon = FioriIcon(resId = R.drawable.ic_sap_icon_notes),
    label = "Attache Medias",
    mimeType = arrayOf("image/*", "video/*", "audio/*")
)

Additionally, you may want to design your own AttachmentActions. If, for example, you want to create a FioriAttachmentAction named PhotoPickerAttachmentAction that can use Android Photo picker to select the image and video files, you would need to create a subtype of FioriAttachmentAction class and FioriAttachmentActionProcessor class. See the following code snippet:

class PhotoPickerAttachmentAction(processor: PhotoPickerAttachmentActionProcessor) :
    FioriAttachmentAction(
        icon = FioriIcon(resId = R.drawable.ic_photo_camera_black_24dp),
        label = "Photo Picker",
        processor = processor
    )

class PhotoPickerAttachmentActionProcessor(
    resultHandler: FioriAttachmentActionResultHandler,
    val launcher: ManagedActivityResultLauncher<PickVisualMediaRequest, List<Uri>>
) : FioriAttachmentActionProcessor(
    resultHandler = resultHandler
) {
    override fun runAction(context: Context, action: FioriAttachmentAction) =
        launcher.launch(PickVisualMediaRequest())
}

fun buildPhotoPickerAttachmentActionProcessor(
    resultHandler: FioriAttachmentActionResultHandler = FioriAttachmentActionResultHandler()
): PhotoPickerAttachmentActionProcessor =
    PhotoPickerAttachmentActionProcessor(
        resultHandler = resultHandler,
        launcher = rememberLauncherForActivityResult(
            contract = ActivityResultContracts.PickMultipleVisualMedia()
        ) {
            if (it.isNotEmpty()) {
                val items = mutableListOf<FioriAttachmentItem>()
                for (uri in it) {
                    items.add(FioriAttachmentItem(uri))
                }
                resultHandler.onResultAttach(items)
            }
        }
    )

PhotoPickerAttachmentAction(processor = buildPhotoPickerAttachmentActionProcessor())

Attachment Item

The FioriAttachmentItem is designed to represent an attachment. It provides a set of properties and a range of methods that allow for easy customization.

In terms of display format, by default only the file resource name is displayed. However, you can customize this to show additional information about the file, such as its size, creation date, or any other relevant details. To do this, you can use the FioriAttachmentActionResultHandler class. You will need to create a concrete class based on FioriAttachmentActionResultHandler, which will allow you to customize the FioriAttachmentItem. Once this is complete, set it to the corresponding FioriAttachmentActionProcessor. Furthermore, you can tailor the FioriAttachmentActionResultHandler to verify the file size and format before attaching, set a progress loading indicator for attaching resources, or even replace the default thumbnail icon for the attachment resource. See the following code snippet:

private class CustomerAttachmentResultHandler : FioriAttachmentActionResultHandler() {
    override fun onResultFormat(uri: Uri, context: Context): FioriAttachmentItem? {
        val item = super.onResultFormat(uri, context)
        item?.let {
            item.secondaryInfo = if (item.file.mimeType().contains("pdf"))
                item.file.extension()
                    .plus(", ${item.file.formattedFileSize()}") else item.file.formattedFileSize() // set the file size as the tertiary information
            tertiaryInfo = itemFile.formattedFileDate()        // set the creation data of file as the tertiary information

            if (item.file.extension().contains("zip") && item.file.fileSize() > (4 * 1024 * 1024)) verify the file size and format for attaching
                return null

            item.updateDisplayLoadingIndicator(true)  // set progress loading indicator
        }

        return item
    }
}

private class CustomThumbnailAttachmentResultHandler : FioriAttachmentActionResultHandler() {

    override fun onResultFormat(uri: Uri, context: Context): FioriAttachmentItem? {
        val itemFile = DefaultFioriAttachmentItemFile(uri)
        itemFile.parse(context)

        return FioriAttachmentItem(file = itemFile, thumbnailHandler = object :
            DefaultFioriAttachmentItemThumbnailHandler() {

            @Composable
            override fun getThumbnail(
                itemFile: FioriAttachmentItemFile,
                viewModeType: FioriAttachmentViewModeType,
                colors: FioriAttachmentColors
            ): FioriImage = if (itemFile.mimeType().contains(documentMimeType))

                FioriImage(
                    resId = R.drawable.ic_sap_icon_measurement_document,
                    color = colors.attachmentItemThumbnailTintColor().value,
                    contentDescription = getThumbnailContentDescription(itemFile)
                )
            else super.getThumbnail(itemFile, viewModeType, colors)
        })
    }
}

Customization Customization

Additionally, you can control the display of a loading indicator in an attachment by setting the displayLoadingIndicator property of the FioriAttachmentItem during initialization. Alternatively, you can use the updateDisplayLoadingIndicator method to update the status of the loading indicator.

Similarly, the enableDeleteAction property controls the enablement of the delete icon button for an attachment. You can also use the updateEnableDeleteAction method to change its status. The delete icon supports additional customizations: you can configure a custom delete icon for all attachments by setting the deleteActionIcon parameter via the FioriAttachmentDefaults.styles method. For individual attachments, you can specify the deleteIcon property of the FioriAttachmentItem to customize the icon.

The annotatedSecondaryInfo property stores an instance of FioriAttachmentItemAnnotationInfo, which presents styled information for an attachment. It takes precedence over the secondaryInfo property. For example, if both annotatedSecondaryInfo and secondaryInfo are set, the value of annotatedSecondaryInfo will be displayed. You can control when to display the value of annotatedSecondaryInfo by setting the corresponding values for displayOnlyInEditMode, displayInViewListLayout, and displayInViewGridLayout properties of the FioriAttachmentItemAnnotationInfo.

Limit Attachment

The attachment control allows you to limit the maximum number of attachments. You can set this limit using the maxAttachmentsNumber parameter of the FioriAttachmentDefaults.styles method. Once the maximum is reached, the Add button will be disabled in edit mode. It’s important to ensure that the number of attachments does not exceed this limit, as doing so may trigger an IllegalArgumentException and potentially crash the app.

You can display a subset of attachments by setting the visibleConfiguration parameter of the attachment control. This parameter accepts an instance of FioriAttachmentVisibleConfiguration, allowing you to specify the number of attachments to display. Only the specified number of attachments will be displayed, and a button will be provided to expand or collapse the attachments list. See the following code snippet:

val maxAttachmentsNumber = 6
val multiFileProcessor = FioriAttachmentActionProcessorBuilder.buildSelectMultiFilesActionProcessor()
val actions = listOf(FioriAttachmentActionSelectFile(processor = multiFileProcessor))
val attachments = rememberSaveableFioriAttachmentItemList(listOf())

val label = rememberSaveable { mutableStateOf("") }
label.value = "${attachments.size} out of $maxAttachmentsNumber attachments uploaded." +
                                "You can add up to $maxAttachmentsNumber attachments."

FioriAttachment(
    verticalScrollable = false,
    headerLabelMsg = label.value,
    actions = actions,
    attachments = attachments,
    visibleConfiguration = (3),
    onAttachmentsAdd = { items ->
        val addedResults = items.reversed().subList(
            0,
            min(maxAttachmentsNumber.minus(attachments.size), items.size)
        )
        if (addedResults.isNotEmpty()) attachments.addAll(0, addedResults)
    },
    onAttachmentItemDelete = {
        attachments.remove(it)
    },
    styles = FioriAttachmentDefaults.styles(
        maxAttachmentsNumber = maxAttachmentsNumber
     )
)

Customization Customization


Last update: August 7, 2024