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 preconfigured 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¶
Using AttachmentFormCell
within your app is typically a 3 step process.
Step 1: Adding AttachmentFormCell
to Your Activity¶
The attachment form cell can be used within your activity like any traditional Android view.
<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:
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.
Step 2: Configuring AttachmentFormCell
¶
In order to attach files to the cell, the user should be able to:
- Pick a variety of files such as PDFs, documents, images, or zip files
- 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
As an app developer you may want to configure the cell to allow only specific types of attachments. To fulfill all these requirements, attachment form cell depends
on the AttachmentAction
class.
Attachment Action¶
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 predefined 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:
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);
Step 3: Receiving the Selected Attachments¶
When end user of your app, is done interacting with the cell, i.e selecting attachment, Android SDK sends results back to the activity containing the FormCell
.
As shown in below code Activity requires to catch the results in onActivityResult
method and relay them to AttachmentFormCell
using onReceiveSelection
method on
the cell. AttachmentFormCell
knows how to identify and decode attachments.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
AttachmentFormCell.onReceiveSelection(requestCode, resultCode, data, this);
}
You have set up your attachment form cell, added all the supported attachment actions and handled the results back. Now the cell is ready for use.
Listening To Changes¶
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<List<Attachment>>
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.
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 user interaction with the form cell, use AttachmentItemClickListener
. AttachmentItemClickListener
provides different interfaces for the user interactions. The following is a code snippet demonstrating the AttachmentItemClickListener
:
/* 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) {
}
});
Default and Custom Attachment Actions¶
SDK offers various prebuilt AttachmentActions
, listed in the below table:
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 |
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.xml
file:
<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
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="fiori_attachment_capture_file_path"
path="." />
<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
.
AttachmentActionTakePicture(@NonNull String title, @NonNull AttachmentFormCell attachmentFormCell, @NonNull String authority)
Now you can create an object of AttachmentActionTakePicture
by providing the authority.
new AttachmentActionTakePicture("Take Picture", mAttachmentFormCell, "com.sap.cloud.mobile.fiori.demo.attachment.provider")
Custom AttachmentAction
¶
Though attachment form cells come with predefined 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
.
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¶
AttachmentFormCell
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 URI of the attachment files as attachments in the cell.
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<Attachment>
¶
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 URI. If the application does not have the permissions to read the URI, you may receive a FileUriExposed
exception.