Post updated on 1st October, 2017 at 06:50 pm
আমার জবের ইন্টারভিউয়ে জিজ্ঞেস করেছিল সার্ভারে ফাইল আপলোড করার কাজ করেছি কিনা। উত্তরটা ছিল ‘না’। ক্লায়েন্টের একটা প্রোজেক্টে ইমেজ আপলোড করার দরকার ছিল। সেখানে ইমেজকে base64 এ কনভার্ট করে স্ট্রিং হিসাবে ডেটা POST করেছিলাম! 😛
আর কোথাও ফাইল আপলোড করা দরকার হয় নাই। জবে জয়েন করার পর প্রথম যেই প্রোজেক্টটা একা হাতে করি সেটাতেই ছিল সার্ভারে ফাইল আপলোড করার ভয়ংকর (!) কিছু কাজ। File create করা, সেগুলোকে ZIP করে সার্ভারে পাঠানো। বেশ কিছু ঝামেলায় পড়েছিলাম কিছু কিছু কাজ করতে। সেখানকার কিছু টপিক নিয়ে আলাদা আলাদা পোস্ট করার ইচ্ছা আছে। আপাতত এই পোস্টে আলোচনা করব কিভাবে Retrofit Library ব্যবহার করে Android App থেকে server এ image বা যে কোনো ফাইল আপলোড করা যায়।
Problem Description
UI-তে একটা “ADD PHOTO” বাটন থাকবে। ক্লিক করলে image gallery open হবে। সেখান থেকে image pick করলে সেটা UI এর ImageView-তে দেখাবে। সাথে থাকবে ২টা EditText. একটায় sender name অপরটায় sender age ইনপুট নেয়া হবে। ইমেজ সিলেক্ট করার পর “UPLOAD” বাটন visible হবে। “UPLOAD” বাটনে ক্লিক করলে image আর JSON string হিসাবে sender এর information-গুলো আপলোড হবে। JSON object এ key হিসাবে থাকবে “sender_name” ও “sender_age”. তো সার্ভারে ফাইল আপলোড হবার পর একটা রেসপন্স মেসেজ আসবে। সেই মেসেজটা একটা TextView তে শো করাবো। GIF image টা দেখলে এখন আশা করি ক্লিয়ার বুঝা যাবে।
Pick image from Gallery and upload to server using Retrofit
পোস্টে পুরো প্রোজেক্টের মেইন অংশগুলো নিয়ে আলোচনা করা হবে। আপনি যদি Retrofit Library এর ব্যাসিক না জেনে থাকেন বা ভুলে গিয়ে থাকেন তাহলে এখান থেকে Retrofit Library ব্যবহার করে GET ও POST method এর ব্যবহার দেখে আসতে পারেন। Manifest ফাইলে প্রয়োজনীয় permission ও MainActivity তে Runtime permission নেয়া হয়েছে। সেগুলোও এই পোস্টে আলোচনা করা হবে না।
Image Pick from Gallery
“ADD PHOTO” বাটনের onClick মেথডে নিচের কোডটুকুর মাধ্যমে image gallery open করা হচ্ছেঃ
Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, PICK_PHOTO); //PICK_PHOTO can be any constant value
একটা ইমেজ সিলেক্ট করার পর সেটাকে ImageView তে দেখাব। আর তার path-টা বের করে রাখব। যেন “UPLOAD” বাটনে ক্লিক করলে ইমেজটা আপলোড করে দেয়া যায়।
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK && requestCode == PICK_PHOTO) { Uri imageUri = data.getData(); filePath = getPath(imageUri); imageView.setImageURI(imageUri); uploadButton.setVisibility(View.VISIBLE); } }
এখানে getPath() একটা user defined method. এর মাধ্যমে ইমেজ ফাইলের path বের করা হয়েছেঃ
private String getPath(Uri uri) { String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = managedQuery(uri, projection, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(column_index); }
Image upload to server from Android App
ইমেজ আপলোড করার মেথডটা MainActivity তে না রেখে আলাদা একটা ক্লাস বানিয়ে তাতে static method হিসাবে রেখেছি। এতে করে MainActivity তে কোডের পরিমান কমবে। দেখতে সুন্দর লাগবে। হিজিবিজি দেখাবে না। আর আসল সুবিধা হচ্ছে এই অ্যাপে যদি আরো কোনো Activity add করি এবং সেখান থেকেও যদি ইমেজ আপলোড করার একই কাজটাই করতে হয় তাহলে সেখানে কোড রিপিট করতে হবে না। এই মেথড কল করেই কাজ আদায় করে নেয়া যাবে।
ধরে নিলাম আপনি Retrofit এর ব্যাসিকগুলো জানেন। সে হিসাবেই বলছি যে Retrofit এর জন্য একটা interface class লিখতে হয়। যাতে GET, POST, PUT, PATCH ইত্যাদি method signature গুলো লিখে রাখা হয়। সেখানে ফাইল আপলোডের জন্য একটা method এর signature লিখতে হবে।
public interface ApiInterface { @Multipart @POST("file_upload_api/upload.php") Call<ResponseModel> fileUpload( @Part("sender_information") RequestBody description, @Part MultipartBody.Part file); }
এই multipart মেথডের প্রথম পার্টে পাঠানো হবে sender information এর JSON string. আর দ্বিতীয় পার্টে পাঠাবো ইমেজ ফাইল। আপনার যদি একাধিক ইমেজ পাঠাতে হয় তখন দ্বিতীয় পার্টে Multipart array হিসাবে পাঠাতে পারবেন। একটু গুগল করলে পেয়ে যাবেন। আর আমি এক্ষেত্রে আলাদা আলাদা ইমেজ না পাঠিয়ে সবগুলোকে ZIP করে পাঠাতে পছন্দ করি। সার্ভার সাইডের কোডও এক্ষেত্রে কম সময়ে করা যায়। কিছুটা ঝামেলা কমে। এই পোস্টে ZIP করা নিয়ে কিছু বলব না। চেষ্টা করব আলাদা পোস্ট করার।
MainActivity এর “UPLOAD” বাটনে ক্লিক করলে এই মেথডটা কল হবেঃ
public static void fileUpload(String filePath, ImageSenderInfo imageSenderInfo) { ApiInterface apiInterface = RetrofitApiClient.getClient().create(ApiInterface.class); Logger.addLogAdapter(new AndroidLogAdapter()); File file = new File(filePath); //create RequestBody instance from file RequestBody requestFile = RequestBody.create(MediaType.parse("image"), file); // MultipartBody.Part is used to send also the actual file name MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile); Gson gson = new Gson(); String patientData = gson.toJson(imageSenderInfo); RequestBody description = RequestBody.create(okhttp3.MultipartBody.FORM, patientData); // finally, execute the request Call<ResponseModel> call = apiInterface.fileUpload(description, body); call.enqueue(new Callback<ResponseModel>() { @Override public void onResponse(@NonNull Call<ResponseModel> call, @NonNull Response<ResponseModel> response) { Logger.d("Response: " + response); ResponseModel responseModel = response.body(); if(responseModel != null){ EventBus.getDefault().post(new EventModel("response", responseModel.getMessage())); Logger.d("Response code " + response.code() + " Response Message: " + responseModel.getMessage()); } else EventBus.getDefault().post(new EventModel("response", "ResponseModel is NULL")); } @Override public void onFailure(@NonNull Call<ResponseModel> call, @NonNull Throwable t) { Logger.d("Exception: " + t); EventBus.getDefault().post(new EventModel("response", t.getMessage())); } }); }
ফাইল আপলোডের callback এ EventBus Library ব্যবহার করে একটা ইভেন্ট পোস্ট করা হয়েছে। এই ইভেন্টকে MainActivity থেকে সাবস্ক্রাইব করা হয়েছে। সাবস্ক্রাইব মেথডে TextView তে রেসপন্সটা দেখানো হয়েছে। একই সাথে Logger Library ব্যবহার করে Log cat এ server response প্রিন্ট করা হয়েছে।
PHP server side (API endpoint) code to receive uploaded image
আপনার লাইভ সার্ভার না থাকলেও localhost এ চাইলে নিচের এই PHP কোডটুকু লিখে একই নেটওয়ার্কের মধ্য থেকে ইমেজ আপলোড করতে পারবেন।
< ? php $target_dir = "files/"; //folder name $target_file = $target_dir . basename($_FILES["file"]["name"]); $response = array('success' => false, 'message' => 'Sorry, there was an error uploading your file.'); $data = $_POST['sender_information']; $json_data = json_decode($data , true); $sender_name = $json_data['sender_name']; $sender_age = $json_data['sender_age']; if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_file)) $response = array('success' => true, 'message' => 'Hello '.$sender_name.'! You are '.$sender_age.' years old. Your image is uploaded successfully!'); echo json_encode($response); ? >
আপনার পিসির localhost যেই ফোল্ডারে পয়েন্ট করে আছে (Windows এর ক্ষেত্রে htdocs, Linux এর ক্ষেত্রে www > html) সেখানে “file_upload_api” নামের একটা ফোল্ডার খুলে সেখানে upload.php নাম দিয়ে উপরের এই PHP script-টি সেভ করুন। “file_upload_api” ফোল্ডারের ভিতরেই upload.php এর সাথে “files” নামের একটা ফোল্ডার খুলুন। এই ফোল্ডারের ভিতরে আপনার আপলোড করা ইমেজগুলো স্টোর হবে।
যেহেতু আপনি আপনার পিসিকেই সার্ভার হিসাবে ব্যবহার করতে চাচ্ছেন সুতরাং আপনার পিসির IP address টা Retrofit এর instance বানানোর সময় base url হিসাবে সেট করে দিতে হবে।
সম্পূর্ণ সোর্সকোডটি পাওয়া যাবে আমার গিটহাব রিপোজিটরিতে। কোথাও কোনো আপডেটের প্রয়োজন অনুভব করলে কমেন্ট করতে ভুলবেন না। চাইলে গিটহাবে pull request পাঠাতে পারেন। আমি merge করে নিব।
Thanks, vaia. worked like charm for me. I was looking for this solution for the past 3 weeks. Don’t care what people say about you. Just carry on spreading the knowledge.
It would be better if you could post something like connecting mysql via retrofit.
Thank you for your comment. I’ll try my best. 🙂
Actually connecting with remote MySQL database is not responsibility of Android. From Android App you can send API call to your remote server (written in PHP, Python or others). Then the server side code connect with MySQL or other database. If you want to send RESTful API call from your App check this blog post. If you want to learn about PHP-MySQL connection then you can follow this tutorial.
I thought about it and just added some myql query in your php script and got what i wanted. Waiting for your next write-ups.
ধন্যবাদ আপনাকে খুব সহজভাবে বুঝিয়ে দেয়ার জন্য। কিভাবে অন্যান্য ফাইল ফরম্যাট যেমন .CSV, .PDF, .DOCS ইত্যাদি পাঠানো যায়? 🙂
আসসালামু আলাইকুম ভাইয়া,
ভাইয়া আমি কোডটি ইমপ্লিমেন্ট করার পর নিম্মের ইররটি পাচ্ছিঃ
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
দেখে মনে হচ্ছে রিটার্ণ করা রেস্পন্সটির জেসন ফরমেট ঠিক নাই। এই ক্ষেত্রে কি করতে পারি? গুগল করলাম কিন্তু ভালো কোনো উত্তর পেলাম না। কি করা যেতে পারে ভাইয়া?