How to implement file upload in Android using TalkJS

·

4 min read

TalkJS has support for file attachments with various file formats allowed. However when implementing a Native Android App using Kotlin or Java, the WebView by default does not provide the ability to upload files. This is not made clear in Android’s developer pages and documentation. This tutorial will show you how to add file upload to your app’s native WebView implementation.

Prerequisites

For this tutorial we shall be using Kotlin. The concepts, types and method names being exactly the same as in Java with the differences only being in the syntax. Similarly, Android Studio and IntelliJ can convert Kotlin code to Java.

We will assume you already have an Android project setup in Android Studio or your favourite editor. Add a WebView to your Activity’s layout XML file. A simple implementation as the one below should be enough:

<WebView
   android:id="@+id/webview"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
 />

Make sure that you have the appropriate permissions in your AndroidManifest.xml file to allow us to upload files.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

In your Activity class, you’ll need to add 2 fields. This first, named: filePath, will hold the URI of the file the user wants to upload.

var filePath: ValueCallback<Array<Uri>>? = null;

The second, html, contains the html document that will be loaded in the WebView. We are hardcoding it as a string just to simplify the code we’ll need to implement. Don’t forget to replace the string, YOUR_APP_ID, with your TalkJS app ID from the dashboard.

val html = """
   <!DOCTYPE html>
   <head>
     <script>
       (function(t,a,l,k,j,s){
       s=a.createElement('script');s.async=1;s.src="https://cdn.talkjs.com/talk.js";a.head.appendChild(s)
       ;k=t.Promise;t.Talk={v:3,ready:{then:function(f){if(k)return new k(function(r,e){l.push([f,r,e])});l
       .push([f])},catch:function(){return k&&new k()},c:l}};})(window,document,[]);
      </script>
   </head>
   <body>
     <div id="talkjs-container" style="width: 100%; height: 600px">
     </div>
   </body>
   <script>
     Talk.ready.then(function() {
       var me = new Talk.User({
         id: '432156789',
         name: 'Sebastian',
         email: 'Sebastian@example.com',
         photoUrl: 'https://demo.talkjs.com/marketplace_demo/img/sebastian.jpg',
         welcomeMessage: null,
         role: 'default',
       });
       var other = new Talk.User({
         id: '123456789',
         name: 'Alice',
         email: 'alice@example.com',
         photoUrl: 'https://demo.talkjs.com/marketplace_demo/img/alice.jpg',
         welcomeMessage: null,
         role: 'default',
       });

       window.talkSession = new Talk.Session({
         appId: 'YOUR_APP_ID',
         me: me,
       });

       var conversation = talkSession.getOrCreateConversation(Talk.oneOnOneId(me, other));
       console.log(conversation);

       conversation.setParticipant(me);
       conversation.setParticipant(other);

       conversation.setAttributes({ welcomeMessages: ["New conversation"], subject: "Conversation 1" });

       var chatbox = talkSession.createChatbox(conversation);
       chatbox.mount(document.getElementById('talkjs-container'));

       chatbox.select(conversation);
     });
   </script>

   </html>
""".trimIndent()

Extending the WebChromeClient Class

One of the most critical steps to enabling file uploads is by implementing a subclass of android.webkit.WebChromeClient. The subclass will need access to the Activity that has the filePath field we defined earlier. We shall do pass this Activity in the primary constructor as shown:

class MyWebChromeClient(val myActivity: Activity) : WebChromeClient(){}

Inside this class, we need to override the method, onShowFileChooser. This method is called when the user clicks a HTML form with the input type as “file”. In our case, this is when a user clicks the file attachment button in the TalkJS UI.

override fun onShowFileChooser(
   webView: WebView?,
   filePathCallback: ValueCallback<Array<Uri>>?,
   fileChooserParams: FileChooserParams?
): Boolean {
   mainActivity.filePath = filePathCallback

   val contentIntent = Intent(Intent.ACTION_GET_CONTENT)
   contentIntent.type = "*/*"
   contentIntent.addCategory(Intent.CATEGORY_OPENABLE)

   mainActivity.startActivityForResult(contentIntent, 1)
   return true
}

We assign the filePath field from earlier to the filePathCallback parameter passed. Next, we create an Intent with the action, GET_CONTENT. This action specifies that we want the user to select a particular kind of file/data. Next, we indicate the MIME type of the data that we expect. "/" allows us to accept a wide range of data types. Adding the category OPENABLE to the intent indicates to the system that we only expect URIs that can be opened as a stream. This is useful due to the wide range of files that we accept. Finally, we pass the intent to startActivityForResult.

Overriding onActivityResult

Once the user selects a file, our Activity's onActivityResult method will be triggered with the URI of the file being passed as a result. The important thing to do here is to use the filePath field we defined earlier to pass the URI back to the WebView and subsequently stream data to the TalkJS UI so that it can be uploaded. This is done by calling the onReceiveValue, passing the URI retrieved from the Intent data passed as a parameter.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
   super.onActivityResult(requestCode, resultCode, data)

   if (resultCode == Activity.RESULT_CANCELED) {
       filePath?.onReceiveValue(null)
       return
   } else if (resultCode == Activity.RESULT_OK) {
       if (filePath == null) return

       filePath!!.onReceiveValue(
           WebChromeClient.FileChooserParams.parseResult(resultCode, data)
       )f
       filePath = null
   }
}

Included are checks to determine if the user canceled the operation. In that case, we just need to pass null to onReceiveValue.

Wrapping Up

Finally, we need to setup our WebView in our Activity’s onCreate method. Here we assign the webview an instance of our WebChromeClient class that we defined earlier. We can then finally load our HTML string.

val webView: WebView = findViewById(R.id.webview)
webView.settings.javaScriptEnabled = true
webView.webChromeClient = MyWebChromeClient(this)

webView.loadDataWithBaseURL(
    "https://talkjs.com", html, "text/html", null, "https://talkjs.com")

With that, your users should be able to upload their memes, documents or videos to each other.