Skip to content

The Attachment Form Cell

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

This form cell has two modes of operation: edit mode and view mode.

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

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

Using the Attachment Form Cell

The attachment form cell can be used within your activity like any traditional Android view.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<com.sap.cloud.mobile.fiori.attachment.AttachmentFormCell
        android:id="@+id/attachmentFormCell"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:adjustViewBounds="true"
        tools:minHeight="100dp"
        app:isEditable="true"
        app:layout_constraintVertical_bias="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </com.sap.cloud.mobile.fiori.attachment.AttachmentFormCell>

Notice that the cell is set in edit mode using app:isEditable="true". You can also fetch the view in your activity and set it to be editable programmatically as shown below:

1
2
       AttachmentFormCell mAttachmentFormCell = findViewById(R.id.attachmentFormCell);
       mAttachmentFormCell.setIsEditable(true);

Warning

Internally, attachment form cells uses a recycler view to display the list of attachments. The height attribute of the form cell should be set as either match_parent, match_constraint, or some fixed height. Wrap_content can cause the attachment form cell to take up the entire screen and push views below the attachment form cell outside the viewable area.

Once you have set up the attachment form cell in the activity, you can add some attachment actions to the cell.

Attachment Action

In order to attach files to the cell, the user should be able to:

  • pick a variety of files such as pdfs, docs, images, or zips
  • pick the files from variety of sources such as Google Photos, Google Drive, Dropbox, or local storage
  • pick files using different mechanisms such as attachment from Camera or storage

To fulfill all these requirements, attachment form cell depends on the AttachmentAction class. AttachmentAction defines the type of file and method to pick those files. For example, AttachmentActionTakeVideo allows the user to capture video using the camera and attach that to the form cell. Similarly, AttachmentActionSelectDocuments allows the user to pick pdf documents. There are multiple different pre-defined AttachmentActions provided with the library. Later in the document we discuss how to create new AttachmentActions.

You can provide a list of supported AttachmentActions for your app using addAttachmentActions(List\<AttachmentAction> actions) API:

1
2
3
4
5
6
7
    List<AttachmentAction> actions = new ArrayList<>();
    actions.add(new AttachmentActionSelectPicture("Gallery", mAttachmentFormCell));
    actions.add(new AttachmentActionSelectFile("Attach File", mAttachmentFormCell));
    actions.add(new AttachmentActionSelectDocuments("Attach Document", mAttachmentFormCell));
    actions.add(new AttachmentActionTakeVideo("Capture Video", mAttachmentFormCell));

    mAttachmentFormCell.addAttachmentActions(actions);

Now you have set up your attachment form cell with all the supported attachment actions and the cell is ready for use.

AttachmentActionTakePicture

AttachmentActionTakePicture is a special Attachment action. It allows you to capture an image and attach it to the AttachmentFormcell. 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 with 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 app requires access to the specific location on local storage. You can provide the access by creating a FileProvider. Each FileProvider exposes a specific location and an authority string.

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

AttachmentActionTakePicture is designed to hide these complexities in capturing images and to simplify the process of attaching the captured image to an AttachmentFormCell. However, in order to store the captured image, AttachmentFormCell 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 which provides access to Download directory and exposes an authority: "com.sap.cloud.mobile.fiori.demo.attachment.provider"

In the AndroidManifest file:

1
2
3
4
5
6
7
8
9
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.sap.cloud.mobile.fiori.demo.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 FileProvider declaration above

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="fiori_attachment_capture_file_path"
        path="Android/data/com.sap.cloud.mobile.fiori.demo/files/Pictures" />
    <external-path
        name="downloads"
        path="Download/" />
</paths>
Once you have correctly configured the FileProvider, you can instantiate the AttachmentActionTakePicture object.

Creating an object of AttachmentActionTakePicture is different than other AttachmentActions. As shown below the constructor of AttachmentActionTakePicture requires a third parameter known as Authority, which is the same as Authority in FileProvider.

1
    AttachmentActionTakePicture(@NonNull String title, @NonNull AttachmentFormCell attachmentFormCell, @NonNull String authority)
Now you can create an object of AttachmentActionTakePicture by providing the authority.
1
    new AttachmentActionTakePicture("Take Picture", mAttachmentFormCell, "com.sap.cloud.mobile.fiori.demo.attachment.provider")

Callback Mechanism

The attachment form cell supports callback listeners to notify the application about updates in the attachments.

You can attach two distinct kinds of attachment listeners.

CellValueChangeListener> Callback

This callback is called whenever there is a change in the list of attachments. If the user removes an existing attachment or adds a new attachment then the application is notified of the available attachments.

As shown in the following code snippet, a list of all the available attachments is provided to the application in the callback for post processing. The attachment form cell has a key, which by default (if no cell change listener is attached) shows the number of attachments; however, when a cell change listener is provided, it depends on the String value returned from "updatedDisplayText" method of the CellValueChangeListener. In the example shown below, updatedDisplayText returns the String, which will be used as the key of the Cell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
mAttachmentFormCell.setCellValueChangeListener(new FormCell.CellValueChangeListener<List<Attachment>>() {
            @Override
            public void cellChangeHandler(List<Attachment> value) {
                // Application side code
            }

            @Override
            public CharSequence updatedDisplayText(List<Attachment> value) {
                return String.format(getApplication().getString("Attachments (%1$d)"), value.size());
            }

        });

AttachmentItemClickListener

If you want to have more fine-grained control and want to perform actions based on the the user interaction with the form cell, use AttachmentItemClickListener. AttachmentItemClickListener provides different interfaces for the the user interactions. The following is a code snippet demonstrating the AttachmentItemClickListener:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
        /* If you want to add callbacks for touch events on Attachments. Please notice that this does not override
           default behavior. However it adds the new behavior on along with the existing one. 
         */
        mAttachmentFormCell.addAttachmentTouchListener(new AttachmentItemClickListener() {
            /**
             * Callback for single tap/click on the Attachment
             *
             * @param view     View
             * @param position int
             */
            @Override
            public void onClick(View view, int position) {
                Toast.makeText(getApplicationContext(), "Opening File", Toast.LENGTH_SHORT).show();
            }

            /**
             * Callback for long press on the Attachment
             *
             * @param view     View
             * @param position int
             */
            @Override
            public void onLongClick(View view, int position) {

            }
            /**
            *
            * Callback for click on the remove icon. Please note that item has already removed from the attachments. Position is the index of the element before removal.
            * 
            * @param int position
            */ 
            @Override
            public void onClickDelete(int position) {

            }
        });

Creating Custom Attachment Actions

The following available AttachmentActions are provided:

Attachment Action Description
AttachmentActionTakeVideo Opens the camera and allows the user to capture video and attach it to the form cell
AttachmentActionTakePicture Opens the camera and allows the user to capture an image and attach it to the form cell
AttachmentActionSelectVideo Allows the user to select video from different sources and attach it to the form cell
AttachmentActionSelectPicture Allows the user to select pictures from different sources and attach them to the form cell
AttachmentActionSelectMedia Allows the user to select media files (images, videos, and audio files) from different sources and attach them to the form cell
AttachmentActionSelectDocuments Allows the user to select pdf files from different sources and attach them to the form cell
AttachmentActionSelectFile Allows the user to select any file format from different sources and attach it to the form cell

Though attachment form cells come with pre-defined AttachmentActions, you may want to design your own AttachmentActions. If, for example, you want to create an AttachmentAction named AttachmentActionSelectDocs that only works with .docx files, you would need to create a subtype of AttachmentAction class.

AttachmentAction is an abstract class, which offers some commonly used attachment action methods and some overridable interfaces. Abstract methods of AttachmentActions are:

Abstract Method Description
public abstract Intent getSelectingIntent(); What intent should be triggered for selecting the attachments? All of the configuration details such as multi-select, MIME types can be configured in the intent.
public abstract String[] requiredPermissions(); Return array of required permissions

Below is a detailed descriptive code snippet for implementing AttachmentActionSelectDocs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class AttachmentActionSelectDocs extends AttachmentAction {
    /**
      * Constructor
      */    
    public AttachmentActionSelectDocs(String title, AttachmentFormCell attachmentFormCell, @NonNull Activity activity) {
        super(title, attachmentFormCell, activity);
    }

    /**
      * Constructor
      */
    public AttachmentActionSelectDocs(AttachmentFormCell attachmentFormCell, @NonNull Activity activity) {
        this(null, attachmentFormCell, activity);
    }

    /**
     * Get the intent to be used to perform the selection of the files. This is the intent that will be used to trigger the file selection.
     * @return Intent
     */
    @Override
    public Intent getSelectingIntent() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        // To attach the file, we need the read permission
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        // the file should be openable so that the user can peek into the file
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        // the MIME type of the files to be selected
        intent.setType("application/docs");
        // should the user be able to select multiple files?
        if (isSelectMultiple) {
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        }
        return intent;
    }

    /**
     * Get the required permissions
     * @return array of String representing the permissions required to use this facility
     */
    @Override
    public String[] requiredPermissions() {
        // as mentioned in getSelectingIntent method, we need read permission for storage
        return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
    }

    /**
      * What is the icon you want to display for this attachment action? 
      */
    @Override
    public Drawable getIcon() {
        return mAttachmentFormCell.getResources().getDrawable(R.drawable.ic_insert_drive_file_black_24dp, null);
    }
}

Setting Attachments Programmatically

Attachment FormCell allows you to set attachments programmatically. There are two methods to set attachments using code.

Using Intent

This is the recommended way to programmatically set attachments on the cell. To demonstrate this feature, we will programmatically add all the files available in the "Downloads" directory of the device. We also assume that the application has the read permission for Manifest.permission.READ_EXTERNAL_STORAGE.

For more information about permissions, see Permissions Overview.

setValue(Intent intent) API allows you to set the URIs of the attachment files as attachments in the cell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class ProgrammaticAttachmentsActivity extends AbstractDemoActivity {
    AttachmentFormCell mAttachmentFormCell;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_programmatic_attachments);
        mAttachmentFormCell = findViewById(R.id.programmatic_attachments);
        // set the intent containing all the attachments as URIs
        mAttachmentFormCell.setValue(getAllAttachmentsIntent());
    }


    // Create the intent containing the URIs  
    private Intent getAllAttachmentsIntent() {
        // Get the reference to the download directory.
        File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        // Get the list of the files from the download directory
        File[] files = directory.listFiles();

        Intent intent = new Intent(Intent.ACTION_VIEW);
        // Add the read permission on the intent
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // if there are files
        if (files != null) {
            // ClipData is used to provide more than one URI here
            ClipData data = null;
            for (File file : files) {
                // if the file is not the directory
                if (!file.isDirectory()) {
                    // get the URI of the file
                    Uri uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".attachment.provider", file);
                    // Create ClipData object if one not created already
                    if (data == null) {
                        data = new ClipData(new ClipDescription(null, new String[]{getContentResolver().getType(uri)}), new ClipData.Item(uri));
                    } else {
                        // add URI to clip data
                        data.addItem(new ClipData.Item(uri));
                    }
                }
            }
            // set clip data on the intent
            intent.setClipData(data);
        }
        return intent;
    }
}

Using List\

You can simply set the attachments on the attachment form cell by simply providing a list of attachment objects using API setAttachment(List\<Attachment>). However, setAttachment(List\<Attachment>) does not check for permissions on the URIs. If the application does not have the permissions to read the URIs, you may receive a FileUriExposed exception.