Files

You can use Kinvey to store and retrieve binary files of size up to 5TB. Files can be of any format.

The files are automatically enabled for download using a Content Delivery Network (CDN) for massive scale and performance.

Kinvey does not directly serve or accept files. Instead, the Kinvey Files API works by providing a short-lived URL to a third-party cloud storage service from which file(s) can be uploaded or downloaded. Currently, the third-party service used is Google Cloud Storage.

You would typically use the Files API to upload and download:

  • images
  • video files
  • other application-specific files.

Uploading

The library's File API supports uploading a java.io.File directly as well as streaming content from an InputStream.

java.io.File

To upload a file, use myClient.file().upload(). Pass the file content as the first argument, followed by an UploadProgressListener for callbacks.

import com.kinvey.java.core.UploadProgressListener;

...

java.io.File file = new java.io.File(getFilesDir(), "chicken_fried_waffles.txt");
file.createNewFile();

mKinveyClient.file().upload(file, new UploaderProgressListener() {
    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "File upload succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "File upload failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        Log.i(TAG, "upload progress: " + uploader.getUploadState());
        // all updates to UI widgets need to be done on the UI thread
    }

});

Optionally, you can use a FileMetaData object as the first parameter to save additional metadata. This lets you set the File's unique _id, ACLs, mark files as public, and set any other custom attributes your app may need.

import com.kinvey.java.model.FileMetaData;
import com.kinvey.java.model.KinveyMetaData.AccessControlList;
...

FileMetaData metadata = new FileMetaData("myFileID");  //create the FileMetaData object
metadata.setPublic(true);  //set the file to be pubicly accesible
metadata.setAcl(new KinveyMetaData.AccessControlList().setGloballyReadable(true)); //allow all users to see this file
metadata.setFileName("chicken_fried_waffles.txt");

java.io.File file = new java.io.File(getFilesDir(), "chicken_fried_waffles.txt");
file.createNewFile();

mKinveyClient.file().upload(metadata, file, new UploaderProgressListener() {
    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "File upload succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "File upload failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        Log.i(TAG, "upload progress: " + uploader.getUploadState());
        // all updates to UI widgets need to be done on the UI thread
    }
});

InputStream

Alternatively you can upload a file from an input stream.

FileInputStream fIn = new FileInputStream(new File ("eats_bacon.mpg"));

mKinveyClient.file().upload("myFileID", fIn,  new UploaderProgressListener() {
    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "Byte upload succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "Byte upload failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        Log.i(KitchenSink.TAG, "upload progress: " + uploader.getUploadState());

        // all updates to UI widgets need to be done on the UI thread
    }

});

You can also provide a FileMetaData object as the first argument when streaming from an InputStream.

import com.kinvey.java.model.FileMetaData;
import com.kinvey.java.model.KinveyMetaData.AccessControlList;
...

FileMetaData metadata = new FileMetaData("myFileID");  //create the FileMetaData object
metadata.setPublic(true);  //set the file to be pubicly accesible
metadata.setAcl(new KinveyMetaData.AccessControlList().setGloballyReadable(true)); //allow all users to see this file
metadata.setFileName("chicken_fried_waffles.txt");


java.io.File file = new java.io.File(getFilesDir(), "chicken_fried_waffles.txt");
file.createNewFile();

FileInputStream fIn = new FileInputStream(new File ("eats_bacon.mpg"));

mKinveyClient.file().upload(metadata, fIn,  new UploaderProgressListener() {
    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "Byte upload succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "Byte upload failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        Log.i(KitchenSink.TAG, "upload progress: " + uploader.getUploadState());

        // all updates to UI widgets need to be done on the UI thread
    }

});

Uploading publicly readable files

Use the method myFileMetaData.setPublic(true) if you want to upload a publicly-readable file. This means that the download link to the file will not expire until you delete the file through Kinvey. If a user uploads a file that you want other users to be able download, you will have to set the gr (globally readable) attribute to true in the _acl (access control list).

import com.kinvey.java.model.FileMetaData;
...

FileMetaData myFileMetaData = new FileMetaData("myFileID");  //create the FileMetaData object
myFileMetaData.setPublic(true);  //set the file to be pubicly accesible
myFileMetaData.setAcl(new KinveyMetaData.AccessControlList().setGloballyReadable(true)); //allow all users to see this file
metadata.setFileName("chicken_fried_waffles.txt");


java.io.File file = new java.io.File(getFilesDir(), "chicken_fried_waffles.txt");
file.createNewFile();

mKinveyClient.file().upload(myFileMetaData, file, new UploaderProgressListener() {
    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "File upload succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "File upload failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        Log.i(TAG, "upload progress: " + uploader.getUploadState());

        // all updates to UI widgets need to be done on the UI thread
    }

});

Downloading

For downloading files to an OutputStream, you can open a file output stream, by name, then feed the file output stream to the file download.

FileOutputStream fos= new FileOutputStream("mug.png");
mKinveyClient.file().download("myFileID", fos, new DownloaderProgressListener() {

    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "File download succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "File download failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpDownloader downloader) throws IOException {
        Log.i(TAG, "progress updated: "+downloader.getDownloadState());
        // any updates to UI widgets must be done on the UI thread
    }
});

MediaPlayer Streaming

You can use the URI in conjunction with Android's MediaPlayer to stream videos or audio files. This has the distinct advantage of not needing to load the entire resource into memory before handing it off to the media player or bitmap factory. This approach saves on both memory usage and battery life, as the file contents are streamed and accessed at the same time.

mKinveyClient.file().downloadMetaData("myFileID", new KinveyUriCallback() {
    @Override
    public void onSuccess(FileMetaData result) {
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setDataSource(Uri.parse(getDownloadURL()));
        mMediaPlayer.prepare();
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

        // you have 1 hour from the time getDownloadUrl was initiated to begin downloading
        // with the temporary url
        mMediaPlayer.start();
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "error downloading movie.mp4", t);
    }

});

Specifying a custom expiration time

Files can also be retrieved by a query, and you can optionally set a custom expiration time (in seconds) if you need the temporary URL to last longer (or shorter) than one hour.


import com.kinvey.java.Query;

...

FileOutputStream fos= new FileOutputStream("mug.png");
Query q = new Query() //create a new query
q.equals("ttl_in_seconds", 3600);  //set a new ttl for the download URL

mKinveyClient.file().download("myFileID", q, fos, new DownloaderProgressListener() {

    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "File download succeeded.");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "File download failed.", error);
    }

    @Override
    public void progressChanged(MediaHttpDownloader downloader) throws IOException {
        Log.i(TAG, "progress updated: "+downloader.getDownloadState());
        // any updates to UI widgets must be done on the UI thread
    }
});

Deleting

You can permanently remove a file using the delete() method.

mKinveyClient.file().delete("myFileID", new KinveyCallback<Void>() {

    @Override
    public void onSuccess(Void result) {
        Log.i(TAG, "Successfully deleted file");
    }

    @Override
    public void onFailure(Throwable error) {
        Log.e(TAG, "Failed to delete file.", error);
    }

});

Linking Files To Data

The library provides a LinkedData API, which supports associating files with an entity stored in a collection. Files are automatically uploaded when an entity is saved, and automatically downloaded when an entity is retrieved. LinkedData is compatible with the functionality of the AppData API, and the two can be used together to interact with the same collection and entities.

When calling get with LinkedData, an entity is retrieved from a Kinvey Collection and the associated files are then retrieved individually from Kinvey's file management service.

Similarly, with a call to save, all associated files are uploaded, and the entity itself is persisted in a collection.

Defining a linked entity

To define a linked entity, your class should extend LinkedGenericJson instead of GenericJson. This class introduces the putFile(String fileKey) method, which creates a new JSON field in your entity with the String argument your app provides as the JSON key. The API creates a HashMap of metadata about the File, which is persisted with the entity in a Collection. The LinkedData API uses this metadata to upload and download any attached files through our File API.

public class UpdateEntity extends LinkedGenericJson {

    @Key("text")
    private String text;

    public UpdateEntity() {
        //"attachment" is the JSON element used to maintain a Linked File.
        putFile("attachment");
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

Your app must call putFile in the constructor, as this is need for the first time a linked file is downloaded.

Using the API

LinkedData can be accessed through an instance of a Client and both asynchronous methods as well as blocking synchronous methods are provided. All the async methods require a KinveyClientCallback, which can be used to receive updates on the progress of the submission of the entity to a collection. These methods also require an UploadProgressListener or a DownloadProgressListener, so you can update the UI as files are uploaded or downloaded.

Attaching a File

You can use a ByteArrayInputStream to associate a file with a linked entity to be uploaded. A ByteArrayInputStream can be created from a byte[], which can be retrieved from a Java.io.File or from the Android Device's camera.

To attach an image to an entity from the Camera, first convert the Bitmap to a byte[], and then create a new ByteArrayInputStream from this byte[] and finally attach it the linked entity.

//First convert Bitmap `image` to a byte[]
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] bytes = stream.toByteArray();
try{
    stream.close();
}catch (IOException e){}
//create new ByteArrayInputStream from byte[] and associated with linked entity
myLinkedEntity.putFile("attachment", new LinkedFile(filename));
myLinkedEntity.getFile("attachment").setInput(new ByteArrayInputStream(bytes));

The above code will associate the Bitmap image with myLinkedEntity, using the key "attachment"

Retrieving a File

To retrieve Files from a linked entity, you can use a ByteArrayOutputStream. This uses the same association used by attaching a file, however instead of an InputStream it is an OutputStream.

To download a file to your device, first open a FileOutputStream to the destination, and then write the returned ByteArrayOutputStream to this new FileOutputStream.

try {
    FileOutputStream fStream = getApplicationContext().openFileOutput("image.png", Context.MODE_PRIVATE);
    ByteArrayOutputStream bos = result.getFile("image").getOutput();
    bos.writeTo(fStream);
    bos.flush();
    fStream.flush();
    bos.close();
    fStream.close();
} catch (Exception ex) {}

LinkedData makes extensive use of ByteArray streams, so make sure your app calls flush() and close() after a stream has been uploaded or downloaded

Save a linked entity

myClient.linkedData("MyCollection", MyEntity.class).save(currentEntity, new KinveyClientCallback<MyEntity.class>() {
    @Override
    public void onSuccess(MyEntity.class result) {
        //Entity successfully uploaded!
    }

    @Override
    public void onFailure(Throwable e) {
        //Something went wrong!
    }

},new UploaderProgressListener() {
    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        //File upload progress changed!
    }
    @Override
    public void onSuccess(Void result) {
        //File upload finished!
    }

    @Override
    public void onFailure(Throwable error) {
        //File upload failed!
    }
});

Get a linked entity

myClient.linkedData("MyCollection", MyEntity.class).getEntity(entityID, new KinveyClientCallback<MyEntity.class>() {
    @Override
    public void onSuccess(MyEntity.class result) {
        //Entity successfully retrieved!
    }
    @Override
    public void onFailure(Throwable e) {
        //Something went wrong!
    }

}, new DownloadProgressListener() {
    @Override
    public void progressChanged(MediaHttpUploader uploader) throws IOException {
        //File download progress changed!
    }

    @Override
    public void onSuccess(Void result) {
        //File download finished!
    }

    @Override
    public void onFailure(Throwable error) {
        //File download failed!
    }
});

As with the rest of our Library, all callbacks are null-safe and are not required. The above examples are overly explicit, and a much simpler usage is:

myClient.linkedData("MyCollection", MyEntity.class).save(currentEntity, null, null);

This simpler approach will not allow for any callbacks or progress updates, allowing you to only use the callbacks that your project needs.

Got a question?