{"id":21659,"date":"2018-01-15T19:06:32","date_gmt":"2018-01-15T19:06:32","guid":{"rendered":"http:\/\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload"},"modified":"2022-04-05T13:57:07","modified_gmt":"2022-04-05T20:57:07","slug":"impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","title":{"rendered":"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>With more than one billion people using WhatsApp, the platform is becoming a go-to for reliable and secure instant messaging. Having so many users means that data transfer processes must be optimized and scalable across all platforms. WhatsApp technology is touted for its ability to achieve significant media quality preservation when traversing the network from sender to receiver, and this is no easy feat to achieve.<\/p>\n<p>In this post, we will build a simple clone of WhatsApp with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK. The app is built using Pusher to implement real-time features.\nWe\u2019ll do this in two parts, first we\u2019ll build the app with primary focus on the <a href=\"https:\/\/cloudinary.com\/blog\/automating_file_upload_and_sharing\">file upload<\/a> and delivery with Cloudinary. Then in the second part, we\u2019ll show how to apply Cloudinary\u2019s transformation and optimization features to the images.\nTo continue with the project, we\u2019ll work on the assumption that you\u2019re not new to Android development and you\u2019ve worked with custom layouts for <code>CompoundViews<\/code>(a ListView in this case).  If you have not, then check out this <a href=\"https:\/\/android.pcsalt.com\/listview-using-baseadapter-android\/\">tutorial<\/a>.<\/p>\n<h2>Setting up an Android Studio Project<\/h2>\n<p>Follow the pictures below to set up your Android project.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_600,c_fill,f_auto,q_auto\/dpr_auto\/Android_Prj1.png\" alt=\"Create a new Android project\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1200\" height=\"654\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_600,c_fill,f_auto,q_auto\/dpr_auto\/minimum_sdk.png\" alt=\"select minimum sdk\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1200\" height=\"654\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_600,c_fill,f_auto,q_auto\/dpr_auto\/empty_activity.png\" alt=\"select an empty activity\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1200\" height=\"654\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_600,c_fill,f_auto,q_auto\/dpr_auto\/default_activities.png\" alt=\"finish the creation with the default activity names\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1200\" height=\"654\"\/><\/p>\n<p>For this tutorial, we will be using a number of third-party libraries including:<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> Android SDK.<\/li>\n<li>\n<a href=\"https:\/\/pusher.com\/docs\">Pusher<\/a> Android SDK<\/li>\n<li>\n<a href=\"https:\/\/square.github.io\/picasso\/\">Picasso<\/a> for asynchronous image loading<\/li>\n<li>\n<a href=\"https:\/\/square.github.io\/retrofit\/\">Retrofit<\/a> for making asynchronous HTTP requests<\/li>\n<\/ul>\n<p>Open up your app level <code>build.gradle<\/code> file, add the following lines and sync your project :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">implementation group: <span class=\"hljs-string\">'com.cloudinary'<\/span>, <span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">'cloudinary-android'<\/span>, <span class=\"hljs-attr\">version<\/span>: <span class=\"hljs-string\">'1.22.0'<\/span>\nimplementation <span class=\"hljs-string\">'com.pusher:pusher-java-client:1.5.0'<\/span>\nimplementation <span class=\"hljs-string\">'com.squareup.retrofit2:converter-gson:2.3.0'<\/span>\nimplementation <span class=\"hljs-string\">'com.squareup.retrofit2:retrofit:2.3.0'<\/span>\nimplementation <span class=\"hljs-string\">'com.squareup.picasso:picasso:2.5.2'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Before you proceed, create Cloudinary and Pusher accounts. You will need your API credentials to enable communication between your app and Cloudinary\u2019s servers.<\/p>\n<p>Open the <code>AndroidManifest.xml<\/code> file and add the following snippet:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">&lt;application...&gt;\n      ....\n      &lt;meta-data android:name=<span class=\"hljs-string\">\"CLOUDINARY_URL\"<\/span>\n      android:value=<span class=\"hljs-string\">\"cloudinary:\/\/@myCloudName\"<\/span>\n      \n&lt;\/application&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The metadata tag will be used for a one-time lifecycle initialization of the Cloudinary SDK.\nReplace the <code>myCloudName<\/code> with your cloud name, which can be found on your Cloudinary dashboard.<\/p>\n<h2>Set Up A Simple API Server<\/h2>\n<p>Next, you need to create a web server with your Pusher credentials to handle your HTTP requests. You can get them from your account dashboard.<\/p>\n<p>Here\u2019s a breakdown of what the server should do:<\/p>\n<ul>\n<li>The app sends a message via HTTP to the server<\/li>\n<li>Server receives message and emits a pusher event<\/li>\n<li>The app then subscribes to the Pusher event and updates view<\/li>\n<\/ul>\n<p>Here\u2019s a basic example using Node.js :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ Import Dependencies<\/span>\n<span class=\"hljs-keyword\">const<\/span> Express = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'express'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> bodyParser = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'body-parser'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> cors = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'cors'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> low = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'lowdb'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> FileSync = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'lowdb\/adapters\/FileSync'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> uuid = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'uuid\/v4'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> Pusher = <span class=\"hljs-keyword\">require<\/span>(<span class=\"hljs-string\">'pusher'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> pusher = <span class=\"hljs-keyword\">new<\/span> Pusher({\n  appId: <span class=\"hljs-string\">'APP_ID'<\/span>,\n  key: <span class=\"hljs-string\">'APP_KEY'<\/span>,\n  secret: <span class=\"hljs-string\">'APP_SECRET'<\/span>,\n  cluster: <span class=\"hljs-string\">'us2'<\/span>,\n  encrypted: <span class=\"hljs-keyword\">true<\/span>\n});\n<span class=\"hljs-comment\">\/\/ Create an Express app<\/span>\n<span class=\"hljs-keyword\">const<\/span> app = Express();\n<span class=\"hljs-comment\">\/\/ Configure middleware<\/span>\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">cors<\/span>());\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">urlencoded<\/span>({ <span class=\"hljs-title\">extended<\/span>: <span class=\"hljs-title\">false<\/span> }));\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">json<\/span>());\n<span class=\"hljs-comment\">\/\/ Configure database and use a file adapter<\/span>\n<span class=\"hljs-keyword\">const<\/span> adapter = <span class=\"hljs-keyword\">new<\/span> FileSync(<span class=\"hljs-string\">'db.json'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> db = low(adapter);\n<span class=\"hljs-comment\">\/\/ Choose a port<\/span>\napp.set(<span class=\"hljs-string\">'port'<\/span>, process.env.PORT || <span class=\"hljs-number\">8050<\/span>);\napp.post(<span class=\"hljs-string\">'\/messages'<\/span>, (req, res) =&gt; {\n  <span class=\"hljs-comment\">\/\/ Assemble data from the requesting client<\/span>\n  <span class=\"hljs-comment\">\/\/ Also assign an id and a creation time<\/span>\n  <span class=\"hljs-keyword\">const<\/span> post = Object.assign({}, req.body, {\n    id: uuid(),\n    created_at: <span class=\"hljs-keyword\">new<\/span> Date()\n  });\n  <span class=\"hljs-comment\">\/\/ Create post using `low`<\/span>\n  db\n    .get(<span class=\"hljs-string\">'messages'<\/span>)\n    .push(post)\n    .write();\n  <span class=\"hljs-comment\">\/\/ Respond with the last post that was created<\/span>\n  <span class=\"hljs-keyword\">const<\/span> newMessage = db\n    .get(<span class=\"hljs-string\">'messages'<\/span>)\n    .last()\n    .value();\n  pusher.trigger(<span class=\"hljs-string\">'messages'<\/span>, <span class=\"hljs-string\">'new-message'<\/span>, newMessage);\n  res.json(newMessage);\n});\n<span class=\"hljs-comment\">\/\/ Listen to the chosen port<\/span>\napp.listen(app.get(<span class=\"hljs-string\">'port'<\/span>), _ =&gt; console.log(<span class=\"hljs-string\">'App at '<\/span> + app.get(<span class=\"hljs-string\">'port'<\/span>)));\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>With that set, we are ready to start building the app. Let\u2019s begin by customizing the xml files to suit our needs.\nOpen <code>activity_chat.xml<\/code> file and change its content to the one <a href=\"https:\/\/github.com\/christiannwamba\/cl-whatsapp-clone\/blob\/master\/app\/src\/main\/res\/layout\/activity_chat.xml\">in the repository<\/a><\/p>\n<p>Since we\u2019re using ListView to show our chats, we need to create a custom ListView layout.\nSo create a new layout resource file  &#8220;`message_xml` &#8220; and <a href=\"https:\/\/github.com\/christiannwamba\/cl-whatsapp-clone\/blob\/master\/app\/src\/main\/res\/layout\/message_layout.xml\">modify its content<\/a> to feature the necessary view objects required to achieve the chat view.<\/p>\n<p>Next, add two vector assets. We won\u2019t be covering how to do that here. But you can check the official <a href=\"https:\/\/developer.android.com\/studio\/write\/vector-asset-studio.html\">Android documentation<\/a> on how to do it.\nNow our XML files are good to go. Next, we have to start adding the application logic.<\/p>\n<h2>Application logic<\/h2>\n<p>To achieve the desired functionalities of the app, we\u2019ll create two Java Classes   <code>M``essage<\/code> and <code>ListMessagesAdapter<\/code>  and an interface called <code>Constants<\/code><\/p>\n<p>So create a new java class called  <code>M``essage<\/code> and modify its contents as:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Message<\/span> <\/span>{\n    <span class=\"hljs-keyword\">public<\/span> String messageType, message, messageTime, user, image;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Once that\u2019s done, create the Adapter Class and modify its contents as well :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ListMessagesAdapter<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">BaseAdapter<\/span> <\/span>{\n<span class=\"hljs-keyword\">private<\/span> Context context;\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">List<\/span>&lt;Message&gt; messages;\n<span class=\"hljs-keyword\">public<\/span> ListMessagesAdapter(Context context, <span class=\"hljs-keyword\">List<\/span>&lt;Message&gt; messages){\n    this.context = context;\n    this.messages = messages;\n}\n@Override\n<span class=\"hljs-keyword\">public<\/span> int getCount() {\n    <span class=\"hljs-keyword\">return<\/span> messages.size();\n}\n@Override\n<span class=\"hljs-keyword\">public<\/span> Message getItem(int position) {\n    <span class=\"hljs-keyword\">return<\/span> messages.get(position);\n}\n@Override\n<span class=\"hljs-keyword\">public<\/span> long getItemId(int position) {\n    <span class=\"hljs-keyword\">return<\/span> position;\n}\n<span class=\"hljs-keyword\">public<\/span> void add(Message message){\n    messages.add(message);\n    notifyDataSetChanged();\n}\n@Override\n<span class=\"hljs-keyword\">public<\/span> View getView(int position, View convertView, ViewGroup <span class=\"hljs-keyword\">parent<\/span>) {\n    <span class=\"hljs-keyword\">if<\/span> (convertView == <span class=\"hljs-keyword\">null<\/span>){\n        convertView = LayoutInflater.from(context).inflate\n        (R.layout.message_layout, <span class=\"hljs-keyword\">parent<\/span>, <span class=\"hljs-keyword\">false<\/span>);\n    }\n    TextView messageContent = convertView.findViewById(R.id.message_content);\n    TextView timeStamp = convertView.findViewById(R.id.time_stamp);\n    ImageView imageSent = convertView.findViewById(R.id.image_sent);\n    View layoutView = convertView.findViewById(R.id.view_layout);\n    Message message = messages.get(position);\n    <span class=\"hljs-keyword\">if<\/span> (message.messageType.equals(Constants.IMAGE)){\n        imageSent.setVisibility(View.VISIBLE);\n        messageContent.setVisibility(View.GONE);\n        layoutView.setBackgroundColor(context.getResources().getColor\n        (android.R.color.transparent));\n        timeStamp.setTextColor(context.getResources().getColor\n        (android.R.color.black));\n        Picasso.with(context)\n                .load(message.image)\n                .placeholder(R.mipmap.ic_launcher)\n                .into(imageSent);\n    } <span class=\"hljs-keyword\">else<\/span> {\n        imageSent.setVisibility(View.GONE);\n        messageContent.setVisibility(View.VISIBLE);\n    }\n    timeStamp.setText(message.user);\n    messageContent.setText(message.message);\n    <span class=\"hljs-keyword\">return<\/span> convertView;\n}\n}\n<span class=\"hljs-comment\">\/\/ updating the ListView.<\/span>\n<span class=\"hljs-keyword\">public<\/span> void add(Message message){\n    messages.add(message);\n    notifyDataSetChanged();\n}\n<span class=\"hljs-comment\">\/**\nThis method adds a new item to our List&lt;Messages&gt; container and subsequently\nnotifies the ListView holding the adapter of the change by calling the\n\u201cnotifyDataSetChanged()\u201d method.\n**\/<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Finally, let\u2019s create an interface for our constant values:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">public interface Constants {\n    <span class=\"hljs-built_in\">String<\/span> PUSHER_KEY = <span class=\"hljs-string\">\"*******************\"<\/span>;\n    <span class=\"hljs-built_in\">String<\/span> PUSHER_CLUSTER_TYPE = <span class=\"hljs-string\">\"us2\"<\/span>;\n    <span class=\"hljs-built_in\">String<\/span> MESSAGE_ENDPOINT = <span class=\"hljs-string\">\"https:\/\/fast-temple-83483.herokuapp.com\/\"<\/span>;\n    <span class=\"hljs-built_in\">String<\/span> IMAGE = <span class=\"hljs-string\">\"image\"<\/span>;\n    <span class=\"hljs-built_in\">String<\/span> TEXT = <span class=\"hljs-string\">\"text\"<\/span>;\n    int IMAGE_CHOOSER_INTENT = <span class=\"hljs-number\">10001<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The interface file contains variables we will make reference to later in other classes. Having your constant values in the same class eases access to them. One crucial thing to note is that you need your own PUSHER_KEY (you can get it from your profile dashboard on Pusher) and MESSAGE_END_POINT(representing your server link).\nNext, open your <code>MainActivity.java<\/code> file. Add the following method to your <code>onCreate(``)<\/code> method:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-keyword\">@Override<\/span>\nprotected void onCreate(Bundle savedInstanceState){\n  ...\n  <span class=\"hljs-selector-tag\">MediaManager<\/span><span class=\"hljs-selector-class\">.init<\/span>(<span class=\"hljs-selector-tag\">this<\/span>)\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The entry point of the Cloudinary Android SDK is the <code>MediaManager<\/code> class. <code>MediaManager.init(this)<\/code> initiates a one-time initialization of the project with the parameters specified in our metadata tag earlier on. Suffice to say, this initialization can only be executed once per application lifecycle.<\/p>\n<p>Another way to achieve this without modifying the <code>AndroidManifest.xml<\/code> file is to pass an HashMap with the necessary configuration details as the second parameter of the <code>MediaManager.init()<\/code> method:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">Map<\/span> config = <span class=\"hljs-keyword\">new<\/span> HashMap();\nconfig.put(<span class=\"hljs-string\">\"cloud_name\"<\/span>, <span class=\"hljs-string\">\"myCloudName\"<\/span>);\nMediaManager.init(<span class=\"hljs-keyword\">this<\/span>, config);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>For this project, we will be sticking with the former method since we already modified our <code>AndroidManifest.xml<\/code> file.<\/p>\n<h2>Configure Pusher library<\/h2>\n<p>It\u2019s time to configure our Pusher library. Add the following lines of code to your <code>onCreate()<\/code> method below.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">PusherOptions options = <span class=\"hljs-keyword\">new<\/span> PusherOptions();\noptions.setCluster(Constants.PUSHER_CLUSTER_TYPE);\nPusher pusher = <span class=\"hljs-keyword\">new<\/span> Pusher(Constants.PUSHER_KEY, options);\nChannel channel = pusher.subscribe(<span class=\"hljs-string\">\"messages\"<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The snippet above is self explanatory.<\/p>\n<p><strong>messages<\/strong> is the name of the channel you created in your server. Now, we need to subscribe to an event in the <strong>messages<\/strong> channel. Hence, we\u2019ll subscribe to the <strong>new-message<\/strong>  event.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">channel.bind(<span class=\"hljs-string\">\"new-message\"<\/span>, <span class=\"hljs-keyword\">new<\/span> SubscriptionEventListener() {\n    @Override\n    public <span class=\"hljs-keyword\">void<\/span> onEvent(<span class=\"hljs-built_in\">String<\/span> channelName, <span class=\"hljs-built_in\">String<\/span> eventName, final <span class=\"hljs-built_in\">String<\/span> data) {\n        \/....\/\n    }\n});\npusher.connect();\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now, we have successfully tagged to our  <strong>messages<\/strong>  channel and subscribed to the <strong>new-messag****e<\/strong>  event. So, each time we send an HTTP request to the server, it redirects it to Pusher and we get notified of this \u201cevent\u201d in our app, and we can then react to it appropriately in the <code>onEvent(\u2026)<\/code> method.<\/p>\n<h2>Set Up Server Communication with Retrofit<\/h2>\n<p>Before we continue, we need to initialize the Retrofit library to communicate with our server.<\/p>\n<p>To do this, we will create to Java files:<\/p>\n<ul>\n<li>RetrofitUtils<\/li>\n<li>Upload(an Interface)<\/li>\n<\/ul>\n<p>Modify the the RetrofitUtils.java file :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RetrofitUtils<\/span> <\/span>{\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">static<\/span> Retrofit retrofit;\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">static<\/span> Retrofit getRetrofit(){\n        <span class=\"hljs-keyword\">if<\/span> (retrofit != <span class=\"hljs-keyword\">null<\/span>){\n            <span class=\"hljs-keyword\">return<\/span> retrofit;\n        }\n        retrofit = <span class=\"hljs-keyword\">new<\/span> Retrofit.Builder()\n                .baseUrl(Constants.MESSAGE_ENDPOINT)\n                .addConverterFactory(GsonConverterFactory.create())\n                .build();\n        <span class=\"hljs-keyword\">return<\/span> retrofit;\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the Upload.java file, we also set it up as so:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">public interface Upload {\n    @FormUrlEncoded\n    @POST(<span class=\"hljs-string\">\"messages\"<\/span>)\n    Call&lt;Void&gt; message(@Field(<span class=\"hljs-string\">\"message\"<\/span>) <span class=\"hljs-built_in\">String<\/span> message, @Field(<span class=\"hljs-string\">\"user\"<\/span>)\n    <span class=\"hljs-built_in\">String<\/span> user);\n    @FormUrlEncoded\n    @POST(<span class=\"hljs-string\">\"messages\"<\/span>)\n    Call&lt;Void&gt; picture(@Field(<span class=\"hljs-string\">\"message\"<\/span>) <span class=\"hljs-built_in\">String<\/span> message, @Field(<span class=\"hljs-string\">\"user\"<\/span>)\n    <span class=\"hljs-built_in\">String<\/span> user, @Field(<span class=\"hljs-string\">\"image\"<\/span>) <span class=\"hljs-built_in\">String<\/span> imageLink);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This is not a Retrofit tutorial, so I won\u2019t be covering the basics of using the library. There are a number of Medium articles that provide those details. But you can check <a href=\"https:\/\/www.vogella.com\/tutorials\/Retrofit\/article.html\">this article from Vogella<\/a> or <a href=\"https:\/\/code.tutsplus.com\/tutorials\/getting-started-with-retrofit-2--cms-27792\">this one by Code TutsPlus<\/a>.\nWhat you need to know, however, is the reason we are making two POST requests. The first POST request will be triggered in the case the user sends only a text. The second will be triggered in the case of picture upload.<\/p>\n<p>Hence we\u2019ll use this second POST request to handle this part of the tutorial for Image Upload and Delivery using Cloudinary.<\/p>\n<h2>Handling Android File Upload and Delivery with Cloudinary<\/h2>\n<p>Now we\u2019ll start adding the logic and discussing how to achieve the Android file upload features using the Cloudinary account we\u2019ve set up.\nGiven the code complexity of this part, we\u2019ll be walking through it with snippets and providing explanations as we go. To handle the image upload features, we\u2019ll head back to the <code>MainActivity.java<\/code> file and set it up the <code>onClick()<\/code> method :<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">case<\/span> R.id.load_image:\n  Intent  chooseImage = <span class=\"hljs-keyword\">new<\/span> Intent();\n  chooseImage.setType(<span class=\"hljs-string\">\"image\/*\"<\/span>);\n  chooseImage.setAction(Intent.ACTION_GET_CONTENT);\n  startActivityForResult(Intent.createChooser(chooseImage, <span class=\"hljs-string\">\"Select Picture\"<\/span>), Constants.IMAGE_CHOOSER_INTENT);\n  <span class=\"hljs-keyword\">break<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Here we sent an implicit intent for images upload. This would pop up a new activity to select pictures from your phone. Once that is done, we want to get the details of the selected image. This is handled in the <code>onActivityResult(\u2026) method<\/code>. Here\u2019s how we set up the method:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">@override\nprotected <span class=\"hljs-keyword\">void<\/span> onActivityResult(int requestCode, int resultCode, Intent data) {\n<span class=\"hljs-keyword\">super<\/span>.onActivityResult(requestCode, resultCode, data);\n<span class=\"hljs-keyword\">if<\/span> (requestCode == Constants.IMAGE_CHOOSER_INTENT &amp;&amp; resultCode == RESULT_OK){\n    <span class=\"hljs-keyword\">if<\/span> (data != <span class=\"hljs-literal\">null<\/span> &amp;&amp; data.getData() != <span class=\"hljs-literal\">null<\/span>){\n        uri = data.getData();\n        hasUploadedPicture = <span class=\"hljs-literal\">true<\/span>;\n        <span class=\"hljs-built_in\">String<\/span> localImagePath = getRealPathFromURI(uri);\n        Bitmap bitmap;\n        <span class=\"hljs-keyword\">try<\/span> {\n            InputStream stream = getContentResolver().openInputStream(uri);\n            bitmap = BitmapFactory.decodeStream(stream);\n            localImage.setVisibility(View.VISIBLE);\n            localImage.setImageBitmap(bitmap);\n        } <span class=\"hljs-keyword\">catch<\/span> (FileNotFoundException e) {\n            e.printStackTrace();\n        }\n        imagePath = MediaManager.get().url().generate(getFileName(uri));\n        typedMessage.setText(localImagePath);\n    }\n}\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next we set up a method that will be triggered whenever the user selects an image. Once this method executes, we will have the URI of the selected image stored in the \u201curi\u201d variable. We monitor the image upload with <code>hasUploadedPicture<\/code> variable. This will be useful in determining which upload interface method to trigger. so we set it up as:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">@Override\npublic <span class=\"hljs-keyword\">void<\/span> onClick(View v) {\n    <span class=\"hljs-keyword\">switch<\/span> (v.getId()){\n        <span class=\"hljs-keyword\">case<\/span> R.id.send:\n<span class=\"hljs-comment\">\/\/                makeToast(\"Send clicked\");<\/span>\n            <span class=\"hljs-keyword\">if<\/span> (hasUploadedPicture){\n<span class=\"hljs-comment\">\/\/                unsigned upload<\/span>\n                <span class=\"hljs-built_in\">String<\/span> requestId = MediaManager.get()\n                  .upload(uri)\n                  .unsigned(<span class=\"hljs-string\">\"sample_preset\"<\/span>)\n                  .option(<span class=\"hljs-string\">\"resource_type\"<\/span>, <span class=\"hljs-string\">\"image\"<\/span>)\n                  .callback(<span class=\"hljs-keyword\">new<\/span> UploadCallback() {\n                    @Override\n                    public <span class=\"hljs-keyword\">void<\/span> onStart(<span class=\"hljs-built_in\">String<\/span> requestId) {\n                        makeToast(<span class=\"hljs-string\">\"Uploading...\"<\/span>);\n                    }\n                    @Override\n                    public <span class=\"hljs-keyword\">void<\/span> onProgress(<span class=\"hljs-built_in\">String<\/span> requestId, long bytes,\n                                           long totalBytes) {\n                    }\n                    @Override\n                    public <span class=\"hljs-keyword\">void<\/span> onSuccess(<span class=\"hljs-built_in\">String<\/span> requestId, <span class=\"hljs-built_in\">Map<\/span> resultData) {\n                        makeToast(<span class=\"hljs-string\">\"Upload finished\"<\/span>);\n                        imagePath = MediaManager.get().url()\n                        .generate(resultData.get(<span class=\"hljs-string\">\"public_id\"<\/span>).toString()\n                        .concat(<span class=\"hljs-string\">\".jpg\"<\/span>));\n                        uploadToPusher();\n                    }\n                    @Override\n                     public <span class=\"hljs-keyword\">void<\/span> onError(<span class=\"hljs-built_in\">String<\/span> requestId, ErrorInfo error) {\n                        makeToast(<span class=\"hljs-string\">\"An error occurred.\\n\"<\/span> + error\n                        .getDescription());\n                    }\n                    @Override\n                     public <span class=\"hljs-keyword\">void<\/span> onReschedule(<span class=\"hljs-built_in\">String<\/span> requestId,\n                                              ErrorInfo error) {\n                        makeToast(<span class=\"hljs-string\">\"Upload rescheduled\\n\"<\/span> + error\n                        .getDescription());\n                }).dispatch();\n            \n            } <span class=\"hljs-keyword\">else<\/span> {\n                upload.message(typedMessage.getText().toString(), <span class=\"hljs-string\">\"Eipeks\"<\/span>\n                ).enqueue(<span class=\"hljs-keyword\">new<\/span> Callback&lt;Void&gt;() {\n                @Override\n                public <span class=\"hljs-keyword\">void<\/span> onResponse(@NonNull Call&lt;Void&gt; call,\n                                       @NonNull Response&lt;Void&gt; response) {\n                     <span class=\"hljs-keyword\">switch<\/span> (response.code()){\n                      <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-number\">200<\/span>:\n                      typedMessage.setText(<span class=\"hljs-string\">\"\"<\/span>);\n                      <span class=\"hljs-keyword\">break<\/span>;\n                }\n            }\n                @Override\n                public <span class=\"hljs-keyword\">void<\/span> onFailure(@NonNull Call&lt;Void&gt; call,\n                @NonNull Throwable t) {\n                   Toast.makeText(Chat.this, <span class=\"hljs-string\">\"Error uploading message\"<\/span>,\n                   Toast.LENGTH_SHORT).show();\n                }\n            });\n         }\n         <span class=\"hljs-keyword\">break<\/span>;\n      <span class=\"hljs-keyword\">case<\/span> R.id.load_image:\n         Intent  chooseImage = <span class=\"hljs-keyword\">new<\/span> Intent();\n         chooseImage.setType(<span class=\"hljs-string\">\"image\/*\"<\/span>);\n         chooseImage.setAction(Intent.ACTION_GET_CONTENT);\n         startActivityForResult(Intent.createChooser(\n         chooseImage, <span class=\"hljs-string\">\"Select Picture\"<\/span>),\n         Constants.IMAGE_CHOOSER_INTENT);\n         <span class=\"hljs-keyword\">break<\/span>;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>At this point we have the URI of the selected image stored in the uri variable and the <code>hasUploaded<\/code> variable should now let us know whether the image upload was successful or not. With this information, we can head on back to the onClick method and upload the selected image to pusher:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">@Override\npublic <span class=\"hljs-keyword\">void<\/span> onClick(View v) {\n    <span class=\"hljs-keyword\">switch<\/span> (v.getId()){\n        <span class=\"hljs-keyword\">case<\/span> R.id.send:\n<span class=\"hljs-comment\">\/\/                makeToast(\"Send clicked\");<\/span>\n            <span class=\"hljs-keyword\">if<\/span> (hasUploadedPicture){\n                <span class=\"hljs-built_in\">String<\/span> requestId = MediaManager.get()\n                .upload(uri)\n                .unsigned(<span class=\"hljs-string\">\"myPreset\"<\/span>)\n                .option(<span class=\"hljs-string\">\"resource_type\"<\/span>, <span class=\"hljs-string\">\"image\"<\/span>)\n                .callback(<span class=\"hljs-keyword\">new<\/span> UploadCallback() {\n                      @Override\npublic <span class=\"hljs-keyword\">void<\/span> onStart(<span class=\"hljs-built_in\">String<\/span> requestId) {\n   makeToast(<span class=\"hljs-string\">\"Uploading...\"<\/span>);\n}\n.........\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>To further explain what went on here, it is worth noting that this method is used for building our image upload request. It contains five synchronized methods:<\/p>\n<ul>\n<li>get()<\/li>\n<li>upload()<\/li>\n<li>option()<\/li>\n<li>callback()<\/li>\n<li>dispatch()<\/li>\n<li>\n<code>upload()<\/code> is an overloaded method however, we\u2019ll be using <code>upload(Uri uri)<\/code> since we already have the uri of the image we want to upload.<\/li>\n<\/ul>\n<p><em><strong>We<\/strong><\/em> <em><strong>need to set an<\/strong><\/em> <a href=\"https:\/\/cloudinary.com\/documentation\/android_image_and_video_upload#unsigned_upload\"><em><strong>unsigned upload preset<\/strong><\/em><\/a> <em><strong>to upload images to our cloud without a secret<\/strong><\/em> <em><strong>key.<\/strong><\/em><\/p>\n<ul>\n<li>\n<code>option()<\/code> takes in two parameters: <code>name<\/code> and  <code>value<\/code>\nIf you are uploading a video, then your value will be <strong>video<\/strong> instead of <strong>image<\/strong>.<\/li>\n<li>\n<code>callback()<\/code> method. This method is used in tracking the progress of the upload. We are using the <code>UploadCallback(){\u2026}<\/code> as its method<\/li>\n<li>\n<code>onSuccess()<\/code> method is triggered upon successful completion of the the media upload. This method contains two parameters: <code>String requestId<\/code> and <code>Map resultData<\/code>\n<\/li>\n<\/ul>\n<p>The <strong>resultData<\/strong> contains information about the uploaded picture. The information we need is the uniquely generated picture name, which can be accessed from the <strong>resultData<\/strong> using the <code>public_id<\/code> as the key.\nCloudinary also enables <code>unique url()<\/code> generation for easy access of the uploaded picture. That\u2019s what we achieved with this bit of code<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">imagePath = MediaManager.get().url().format(<span class=\"hljs-string\">\"webp\"<\/span>). generate(resultData.get(<span class=\"hljs-string\">\"public_id\"<\/span>));\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The line ensures that we can access the image we\u2019ve uploaded using the Retrofit library. With this, we then call the <code>uploadToPusher()<\/code> method.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">private <span class=\"hljs-keyword\">void<\/span> uploadToPusher(){\n    upload.picture(typedMessage.getText().toString(), <span class=\"hljs-string\">\"Eipeks\"<\/span>, imagePath)\n        .enqueue(<span class=\"hljs-keyword\">new<\/span> Callback&lt;Void&gt;() {\n            @Override\n            public <span class=\"hljs-keyword\">void<\/span> onResponse(@NonNull Call&lt;Void&gt; call,\n                                   @NonNull Response&lt;Void&gt; response) {\n                <span class=\"hljs-keyword\">switch<\/span> (response.code()){\n                    <span class=\"hljs-keyword\">case<\/span> <span class=\"hljs-number\">200<\/span>:\n                        localImage.setVisibility(View.GONE);\n                        typedMessage.setText(<span class=\"hljs-string\">\"\"<\/span>);\n                        <span class=\"hljs-keyword\">break<\/span>;\n                }\n            }\n            @Override\n           public <span class=\"hljs-keyword\">void<\/span> onFailure(@NonNull Call&lt;Void&gt; call, @NonNull Throwable t) {\n               Toast.makeText(Chat.this, <span class=\"hljs-string\">\"Failed to upload picture\\n\"<\/span> +\n               t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();\n           }\n        });\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Once this method executes, our HTTP requests reaches the server, which in turn redirects the information we\u2019ve uploaded to Pusher. This information goes to the \u201cmessages\u201d channel. Since, we have subscribed to the \u201cnew-messages\u201d event, our application is notified of this event. All that\u2019s left is for our app to react appropriately to this event. Next, we will modify our <code>onEvent()<\/code> method.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">@Override\npublic <span class=\"hljs-keyword\">void<\/span> onEvent(<span class=\"hljs-built_in\">String<\/span> channelName, <span class=\"hljs-built_in\">String<\/span> eventName, final <span class=\"hljs-built_in\">String<\/span> data) {\n    Gson gson = <span class=\"hljs-keyword\">new<\/span> Gson();\n    final Message message = gson.fromJson(data, Message.class);\n    <span class=\"hljs-keyword\">if<\/span> (hasUploadedPicture){\n        message.messageType = Constants.IMAGE;\n    } <span class=\"hljs-keyword\">else<\/span> {\n        message.messageType = Constants.TEXT;\n    }\n    hasUploadedPicture = <span class=\"hljs-literal\">false<\/span>;\n    messages.add(message);\n    runOnUiThread(<span class=\"hljs-keyword\">new<\/span> Runnable() {\n        @Override\n        public <span class=\"hljs-keyword\">void<\/span> run() {\n            messagesList.setSelection(messagesList.getAdapter().getCount() - <span class=\"hljs-number\">1<\/span>);\n        }\n    });\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This brings us to the end of this part of this tutorial, on the next part we\u2019ll be discussing how to manipulate our uploaded images with Cloudinary to add transformations and optimizations.\nHere\u2019s an image showing how the image upload works thus far:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_300,c_fill\/activities.jpg\" alt=\"Build a WhatsApp clone with image and video upload\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"300\" height=\"533\"\/><\/p>\n<p>Feel free to check the official <a href=\"https:\/\/cloudinary.com\/documentation\/android_integration#getting_started_guide\">documentation here<\/a>. The source code for the project is on <a href=\"https:\/\/github.com\/christiannwamba\/cl-whatsapp-clone\">GitHub<\/a>. In the next part of this article, we will cover how uploaded images can be transformed and what we can get from Cloudinary\u2019s optimization features.<\/p>\n<hr \/>\n<h2>Want to Learn More About File Uploads?<\/h2>\n<ul>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/automating_file_upload_and_sharing\">Automating File Upload and Sharing<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/file_upload_with_php\">Uploading PHP Files and Rich Media the Easy Way<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/file_upload_with_ajax\">AJAX File Upload &#8211; Quick Tutorial &amp; Time Saving Tips<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\">Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/direct_image_uploads_from_the_browser_to_the_cloud_with_jquery\">Direct Image Uploads From the Browser to the Cloud With jQuery<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/file_upload_with_angular_or_angularjs_to_cloudinary\">File Upload With Angular to Cloudinary<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/uploading_vue_files_and_rich_media_in_two_easy_steps\">Uploading Vue Files and Rich Media in Two Easy Steps<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/node_js_file_upload_to_a_local_server_or_to_the_cloud\">Node.js File Upload To a Local Server Or to the Cloud<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/laravel_file_upload_to_a_local_server_or_to_the_cloud\">Laravel File Upload to a Local Server Or to the Cloud<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/blog\/javascript_file_upload_to_cloudinary\">JavaScript File Upload in Two Simple Step<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":21660,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[333,119,263],"class_list":["post-21659","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-android","tag-file-upload","tag-sdk"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Clone WhatsApp Technology to Build a File Upload Android App<\/title>\n<meta name=\"description\" content=\"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App\" \/>\n<meta property=\"og:description\" content=\"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-01-15T19:06:32+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2022-04-05T20:57:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1645221330\/website-2021\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"1540\" \/>\n\t<meta property=\"og:image:height\" content=\"847\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App\",\"datePublished\":\"2018-01-15T19:06:32+00:00\",\"dateModified\":\"2022-04-05T20:57:07+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\"},\"wordCount\":14,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA\",\"keywords\":[\"Android\",\"File-upload\",\"SDK\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2018\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\",\"url\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\",\"name\":\"Clone WhatsApp Technology to Build a File Upload Android App\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA\",\"datePublished\":\"2018-01-15T19:06:32+00:00\",\"dateModified\":\"2022-04-05T20:57:07+00:00\",\"description\":\"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA\",\"width\":1540,\"height\":847},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Clone WhatsApp Technology to Build a File Upload Android App","description":"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","og_locale":"en_US","og_type":"article","og_title":"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App","og_description":"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.","og_url":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","og_site_name":"Cloudinary Blog","article_published_time":"2018-01-15T19:06:32+00:00","article_modified_time":"2022-04-05T20:57:07+00:00","og_image":[{"width":1540,"height":847,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1645221330\/website-2021\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3-jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload"},"author":{"name":"","@id":""},"headline":"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App","datePublished":"2018-01-15T19:06:32+00:00","dateModified":"2022-04-05T20:57:07+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload"},"wordCount":14,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA","keywords":["Android","File-upload","SDK"],"inLanguage":"en-US","copyrightYear":"2018","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","url":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload","name":"Clone WhatsApp Technology to Build a File Upload Android App","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA","datePublished":"2018-01-15T19:06:32+00:00","dateModified":"2022-04-05T20:57:07+00:00","description":"Leverage WhatsApp Technology to build a WhatsApp clone for Android with a focus on showcasing the background image upload process using Cloudinary\u2019s Android SDK.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA","width":1540,"height":847},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/impressed_by_whatsapp_technology_build_a_whatsapp_clone_with_image_and_video_upload#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Impressed by WhatsApp technology? Clone WhatsApp Technology to Build a File Upload Android App"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649722083\/Web_Assets\/blog\/Android_WhatsApp_Clone_Part1_2000x1100_v3\/Android_WhatsApp_Clone_Part1_2000x1100_v3.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21659","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=21659"}],"version-history":[{"count":2,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21659\/revisions"}],"predecessor-version":[{"id":23853,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21659\/revisions\/23853"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/21660"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=21659"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=21659"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=21659"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}