Retrofit library নিয়ে আমার ব্লগের প্রথম পোস্টে আলোচনা করেছিলাম সোজা-সাপটা ভাবে কিভাবে অ্যাপ থেকে সার্ভারে কল করা যায়। দেখিয়েছিলাম Activity থেকেই নেটওয়ার্ক কলের implementation. অর্থাৎ সার্ভারে কল করা, সেখান থেকে রেসপন্স পাওয়া সব কিছুই করা হচ্ছিল Activity class এর ভিতরে। এই পর্বে আমরা কিছু বেটার প্র্যাকটিস অ্যাপ্লাই করব। UI class এর ভিতরে আমরা নেটওয়ার্ক কল করব না। মাঝে একটা abstraction layer নিয়ে আসবো। ফলে view layer এর থেকে network layer কে আলাদা করে ফেলা যাবে। আপনি যদি রেট্রোফিটের ব্যাপারে নতুন হয়ে থাকেন তাহলে এই পোস্টটা কন্টিনিউ না করে আগে রেট্রোফিটের উপর লেখা প্রথম পোস্টটি পড়ে আসেন। তাহলে বুঝতে সহজ হবে।
Software engineering বা Object oriented programming এর কোর্স করার সময় আমরা বার বার দেখেছি concrete implementation না করার জন্য, view layer, data layer, network layer এগুলোকে আলাদা আলাদা করার জন্য। এতে কোডটা আরো বেশি testable হয়। কোডের একেকটা পার্টের সাথে অন্য পার্টের ডিপেনডেন্সি কম থাকে। কিন্তু বেশির ভাগ সময়েই আমরা এই প্র্যাকটিসগুলো ফলো করতে পারি না। এর অন্যতম কারণ হচ্ছে যখন আমরা কোনো একটা প্ল্যাটফর্মের ডেভেলপমেন্ট শিখা শুরু করি তখন সাধারনত এসব বেস্ট প্র্যাকটিসগুলো শেখা হয় না। টিউটোরিয়ালগুলোতে সাধারনত নির্দিষ্ট টপিকটার কাজই বলা থাকে। যেমন ধরা যাক রেট্রোফিটের উপর লেখা আমার প্রথম পোস্টটার কথাই! সেখানে আমরা Activity class এর ভিতর থেকেই সরাসরি retrofit use করে network এ কল করে ডেটা নিয়ে আসছি। সেটা ছিল প্রথম পর্ব। তাই দেখানো হয়েছে কতটা সহজে কাজটা করা যায়। তো আমরা নিয়মিত আমাদের কোডকে more maintainable করতে চাইলে যা শিখেছি সেটার উপরই সন্তুষ্ট থাকলে হবে না। প্রতিনিয়ত আগের কোডগুলোকে রিভিউ করতে হবে। রিফ্যাক্টর করতে হবে। আমাদের আগের কোডগুলোকে রিফ্যাক্টর করার জন্যেই আজকের এই পোস্ট। এখানে আমরা আমাদের ভিউ লেয়ার থেকে নেটওয়ার্ক লেয়ারকে আলাদা করে ফেলব। ভিউ লেয়ার জানবেই না ডেটা রেট্রোফিটের মাধ্যমে কল করে আনা হচ্ছে নাকি ভলি’র মাধ্যমে কল করে আনা হচ্ছে। নাকি নেটওয়ার্ক থেকে আনাই হচ্ছে না, বরং লোকাল্যি dummy data provide করা হচ্ছে। অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং এ আমরা Abstraction পড়েছিলাম। আমরা এখানে এই কনসেপ্টটা ইমপ্লিমেন্ট করব।
Abstraction in Object Oriented Programming
OOP তে abstraction খুবই দরকারি একটা কনসেপ্ট। অ্যাবস্ট্রাকশন বলতে মূলত কোনো একটা কাজের implementation process কে hide করা বুঝায়। ব্যাপারটা কী রকম? ধরুন একটা গাড়ির ব্রেক করার সিসটেমটার কথা। ড্রাইভাররা জানেন ব্রেক প্যাডে চাপ দিলে গাড়ি ব্রেক করে। গাড়ির ক্ষেত্রে যে রকম ব্রেক প্যাড আছে, বিমানের ক্ষেত্রেও হয়ত সে রকম বা কাছাকাছি ধরনের ব্রেক প্যাড আছে। আবার রকেটের ব্রেক প্যাড আর লঞ্চ-জাহাজের ব্রেক প্যাডও হয়ত একই ধরণের কাজ করে (ব্রেক প্যাডে চাপ দিলে ব্রেক করার কাজটাকে একই কাজ বুঝাচ্ছি, ভিতরের কাজ আলাদা)। বাস, রিকসা, সাইকেল, জাহাজ, রকেক, বিমান সবারই হয়ত ব্রেক প্যাড বা ব্রেক করার একটা পদ্ধতি আছে। রকেটের ব্রেক প্যাডে চাপ দিলে হয়ত পিছন দিয়ে একটা প্যারাসুটের মত কিছু একটা উড়বে যেন রকেটের গতি কমে যায়, সাইকেলের ক্ষেত্রে ২ টা রাবার প্যাড চাকাকে চেপে ধরবে, জাহাজের ক্ষেত্রে হয়ত ভারি একটা নোঙ্গর মাটিতে গেঁথে যাবে। সবারই ব্রেক প্যাড আছে, কিন্তু একেক জনের ব্রেক করার সিসটেম একেক রকম। আমাদের দেশে গাড়ি শুধু ড্রাইভারই ব্রেক করতে পারেন না, গাড়ির বাইরে থেকে পথচারীরাও ব্রেক করতে পারেন। দ্রুতগামী গাড়ির সামনে দিয়ে আস্তে ধীরে যেতে যেতে পথচারীরা হাতের ইশারায়ও গাড়ি থামিয়ে দেন। ঢাকায় গত চার বছরের সাইকেল চালানোর অভিজ্ঞতা থেকে দেখেছি এক্ষেত্রে আপু-আন্টিরা সবচেয়ে বেশি সিদ্ধহস্ত! হাতের ইশারায় গাড়ি থামানো এটাও একটা ব্রেকিং সিসটেম। এটার ইমপ্লিমেন্টেশন কী রকম সেটা পথচারীরা জানেন না। তো যাই হোক, এই যে উপর থেকে আমরা একই ভাবে গাড়ি ব্রেক করছি কিন্তু একেকটার ক্ষেত্রে একেক ভাবে ব্রেক হচ্ছে ড্রাইভার জানতে পারছেন না বা জানার দরকার হচ্ছে না ভিতরে কী হচ্ছে এটাই হচ্ছে Abstraction. যদি এই অ্যাবস্ট্রাকশন না থাকত তাহলে ড্রাইভারদের সামনে ইঞ্জিন খুলে দিয়ে রাখা লাগত। আগে তাদেরকে ঐ যানবাহনের ইঞ্জিনিয়ার হওয়া লাগত এরপর তারা ভিতরের ম্যাকানিজম অনুযায়ী গাড়ি ড্রাইভ করত। Abstraction layer এর জন্য এত কষ্ট করার দরকার হচ্ছে না।
গাড়ি-ঘোড়া থেকে এবার একটু অ্যাপ ডেভেলপমেন্টে আসি। আমার Activity class এর দরকার কিছু ডেটা। যেগুলো সে UI তে শো করবে। এই ক্লাসের কিন্তু জানার দরকার নাই ডেটা কোথা থেকে আসবে, কিভাবে আসবে, কোন লাইব্রেরি ইউজ করব ইত্যাদি। এটা আরো বেশি দরকার হবে তখন যখন একই টিমে অনেকজন কাজ করছেন। আপনাকে দায়িত্ব দিলো UI design আর ডেটা UI তে লোড করার জন্য। আরেকজনকে দায়িত্ব দিল সার্ভার থেকে ডেটা নিয়ে এসে সাজিয়ে গুছিয়ে আপনার কাছে (আসলে আপনার Activity’র কাছে) পৌঁছে দেয়ার জন্য। তাহলে দুইজন স্বাধীন ভাবে কিভাবে কাজ করবেন? আপনি UI design করে আপনার কো-ওয়ার্কারকে বলবেন আপনার Activity class এ এসে কোড লিখে দিতে? নাকি বলবেন কিভাবে ডেটা পাবো সেই হিস্টরি বলতে? বেশ ক্যাচাল না?
বাট… কাজটা যদি আমাদের গাড়ির ব্রেক করার সিসটেমের মত হয়? আপনি একটা মেথডে কল করবেন। getData বা এই টাইপের একটা মেথড। এই মেথডের callback এ আপনি ডেটা পাবেন। কেমনে পাবেন সেটা আপনার বিষয় না। আপনার কাজ UI নিয়ে আপনি UI নিয়ে কাজ করবেন, যার কাজ network থেকে কল করে ডেটা নিয়ে আসা সে নেটওয়ার্ক থেকে রেট্রোফিট হোক, ভলি হোক বা যেভাবেই হোক আপনার getData মেথডের callback এ ডেটা পৌঁছে দিবে। তাহলে মাঝে একটা abstract layer যোগ হয়ে গেল। আপনি জানেন না রিয়েল ইমপ্লিমেন্টেশন। ইমপ্লিমেন্টেশনের উপর আপনার Activity class সরাসরি ডিপেনডেন্ট হলো না। এটাই হচ্ছে আলাদা network layer বা abstraction layer. সিনিয়রদের প্রায় সময়ই বলতে শুনবেন “module-wise কোড করো। সবকিছু একসাথে রেখো না। আলাদা আলাদা কাজ আলাদাই রাখো। সব ক্লাসের সাথে সব ক্লাসের স্ট্রং ডিপেন্ডেন্সি রেখো না। মডিউলগুলো loosely coupled রাখো… ইত্যাদি”। এই বিষয়গুলো ইমপ্লিমেন্টের জন্যই এই abstraction টা বুঝতে হবে। নিচে আমরা প্রথম পোস্টের কোডটাকে রিফ্যাক্টর করব। আগের প্রোজেক্টের কোডগুলো দেখে থাকলে পার্থক্যটা সহজে বুঝা যাবে।
Create Project and Some Packages/classes
ধরেই নিচ্ছি আপনি আগের পোস্টটা পড়েছেন বা রেট্রোফিট সম্পর্কে আইডিয়া আছে। তাই বিস্তারিত বলছি না। নতুন একটা প্রজেক্ট খুলে এরকম করে প্যাকেজ আর কিছু ক্লাস/ইন্টারফেস বানিয়ে নিন। এই লিংকে গেলে প্যাকেজ আর ক্লাসগুলোর সম্পূর্ণ কোড পাওয়া যাবে।
NetworkRelatedClass এই প্যাকেজের ভিতরে কিছু নতুন class ও interface যোগ করা হয়েছে। Activity আর Model প্যাকেজ আগের মতই রয়েছে। নেটওয়ার্ক প্যাকেজের ভিতরে RetrofitApiClient ক্লাসটা আগের মতই আছে। কোনো চেঞ্জ করা হয় নি। RetrofitApiInterface নামক interface-টির আগের নাম ছিল ApiInterface. জাস্ট এটা rename করা হয়েছে। নতুন যুক্ত করা হয়েছে MyApiService, NetworkCall ও ResponseCallback. যাদের মধ্যে MyApiService ও ResponseCallback দুটি interface. আর NetworkCall একটি class. আমাদের অ্যাপের যাবতীয় সকল নেটওয়ার্ক রিলেটেড কল করার কাজগুলো হবে NetworkCall ক্লাসের ভিতরে। কোনো Activity class এর ভিতরে নেটওয়ার্কের সাথে কানেকশন সম্পর্কিত কোনো কাজ হবে না। এই কনসেপ্টটাকেই ডায়াগ্রামের সাহায্যে বুঝানোর চেষ্টা করেছি নিচের ছবিতে।
উপরের ছবির Abstraction layer হিসাবে কাজ করবে আমাদের নতুন বানানো MyApiService interface-টি। Network Layer হিসাবে কাজ করবে নতুন বানানো NetworkCall class-টি। এটি MyApiService class-কে implement করবে। ইন্টারফেসে যেই method signature-গুলো ছিলো সেগুলোকে NetworkCall ক্লাস override করবে। আর ডেটা নেটওয়ার্ক থেকে নিয়ে রেডি হয়ে যাবার পর caller class এর কাছে তা পৌঁছে দেবার জন্য কাজ করবে ResponseCallback interface-টি। এই কলব্যাক ইন্টারফেসের onSuccess বা onError মেথডগুলো কল হবে NetworkCall এর ভিতর থেকে। এবার সবগুলো ক্লাসের কোডগুলো দেখে নেয়া যাক।
MyApiService interface
. public interface MyApiService { void userValidityCheck(User userLoginCredential, ResponseCallback<String> callback); void getJokeFromServer(String userId, ResponseCallback<String> callback); }
আমাদের অ্যাপে যতগুলো নেটওয়ার্ক কল করার দরকার হবে সবগুলোর জন্য একটা করে মেথড সিগনেচার এই ইন্টারফেসে উল্লেখ করতে হবে। এই ইন্টারফেসকে যখন NetworkCall class implement করবে তখন এই মেথডগুলোকে override করতে হবে। সেই ওভাররাইড করা method body-গুলোর ভিতর থেকে সত্যিকারের নেটওয়ার্ক কল করা হবে। সার্ভার থেকে ডেটা চলে আসলে UI class বা Activity Class এর কাছে notify করার জন্য আরেকটা ইন্টারফেস ইউজ করা হচ্ছে। সেটা দেখা যাচ্ছে MyApiService এর মেথড সিগনেচারের second parameter এ। ResponseCallback ইন্টারফেস। এই ইন্টারফেসের কোডগুলো দেখে নিই।
ResponseCallback interface
. public interface ResponseCallback<T> { void onSuccess(T data); void onError(Throwable th); }
কোনো Activity বা যে কোনো ক্লাস যদি MyApiService এর userValidityCheck() বা getJokeFromServer() মেথড কল করতে চায় তাহলে অবশ্যই তাকে ResponseCallback ইন্টারফেসটি implement করতে হবে। যদি implement করে তাহলে তাকে অবশ্যই ResponseCallback interface এর দুটি মেথড override করতে হবে। সেগুলো হচ্ছে onSuccess এবং orError. Server থেকে ডেটা চলে আসলে NetworkCall ক্লাসের ভিতর থেকে onSuccess আর কলটা ফেইল করলে onError মেথডটা কল করা হবে। onSuccess কল করার সময় কাংক্ষিত ডেটা onSuccess এর প্যারামিটারে pass করে দেয়া হবে। যা Activity class এর override করা onSuccess মেথডে পাওয়া যাবে। Activity class তখন এই ডেটা নিয়ে যা করা দরকার করবে।
onSuccess এর parameter হিসাবে আমরা কোনো টাইপ ফিক্সড করে দেই নি। যেমন বলতে ResponseCallback এর মেথড সিগনেচারে বলতে পারতাম void onSuccess(String data); তাহলে এই মেথডে শুধু স্ট্রিংই পাস করা যেত। আমরা জেনেরিক টাইপ ইউজ করেছি। T দিয়ে বুঝানো হচ্ছে এখানে যে কোনো টাইপের (object) ডেটা পাঠানো যাবে। String, Integer বা আমাদের লিখা কোনো ক্লাসের অবজেক্ট। এবার দেখি implementation এর কাজটা কিভাবে হচ্ছে।
NetworkClass class for real implementation
. public class NetworkCall implements MyApiService{ @Override public void userValidityCheck(User userLoginCredential, final ResponseCallback<String> userValidityCheckListener) { Logger.addLogAdapter(new AndroidLogAdapter()); RetrofitApiInterface retrofitApiInterface = RetrofitApiClient.getClient().create(RetrofitApiInterface.class); Call<ServerResponse> call = retrofitApiInterface.getUserValidity(userLoginCredential); call.enqueue(new Callback<ServerResponse>() { @Override public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) { Logger.d("Network layer. User validity Raw response: " + response.raw()); ServerResponse validity = response.body(); if(validity!=null){ if(validity.isSuccess()) userValidityCheckListener.onSuccess(validity.getMessage()); else userValidityCheckListener.onError(new Exception(validity.getMessage())); } else userValidityCheckListener.onError(new Exception(response.message())); } @Override public void onFailure(Call call, Throwable t) { userValidityCheckListener.onError(t); } }); } @Override public void getJokeFromServer(String userId, final ResponseCallback<String> getJokeListener) { RetrofitApiInterface retrofitApiInterface = RetrofitApiClient.getClient().create(RetrofitApiInterface.class); Call<ServerResponse> call = retrofitApiInterface.getJoke(userId); call.enqueue(new Callback<ServerResponse>() { @Override public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) { Logger.d("Network layer. get Joke Raw response: " + response.raw()); ServerResponse validity = response.body(); if(validity!=null){ if(validity.isSuccess()) getJokeListener.onSuccess(validity.getMessage()); else getJokeListener.onError(new Exception(validity.getMessage())); } else getJokeListener.onError(new Exception(response.message())); } @Override public void onFailure(Call<ServerResponse> call, Throwable t) { getJokeListener.onError(t); } }); } }
NetworkCall class এ যখনই MyApiService interface-টা implement করেছি সাথে সাথেই এরর দেখানো শুরু করল। Android Studio বলতে লাগলো যে “তুমি MyApiService interface implement করছো এর মানে হইতাছে ঐ ইন্টারফেসের ভিত্রে যতগুলা মেথড সিগনেচার আছে সবগুলা তুমার ওভাররাইড করা লাগবো! অ্যাকটিভিটি থিকা খালি ঐ মেথডের নাম ধৈরা ডাক দিলেই তুমি যা করনের করবা!”। আমাদের আগের পোস্টের প্রোজেক্টটার দিকে যদি তাকান তাহলে দেখবেন Activity’র ভিতরে রেট্রোফিটে কল দিয়েছিলাম। রেট্রোফিটের রেসপন্সের ভিতরে আবার UI নিয়ে কাজ করেছি। মানে অনেকটা UI এর ভিতর network এর কাজ। আবার নেটওয়ার্কের ভিতর আরেক দফা UI এর কাজ। পারফেক্ট খিচুড়ি কম্বিনেশন! কিন্তু আমাদের উপরে থাকা এই NetworkCall ক্লাসের দিকে তাকান। এটা dedicatedly শুধু নেটওয়ার্কের জন্যেই কাজ করবে। এর ভিতের এক লাইনেরও UI related কাজের কোড নাই। হয়ত এতক্ষণে বুঝে গেছেন যে Retrofit এর onResponse আর onFailure মেথডের ভিতর থেকে ResponseCallback interface এর মেথড কল করা হচ্ছে আর প্রয়োজনীয় ডেটা provide করা হচ্ছে। যেখান থেকেই NetworkCall এর মেথড কল করা হোক না কেন সেখানেই কলব্যাক ইন্টারফেসকে ইমপ্লিমেন্ট করা হয়েছে। রেট্রোফিটের কল শেষে কলব্যাক ইন্টারফেসের মেথডে কল দিলে এই ডেটাটা আমাদের অ্যাক্টিভিটির কাছে পৌঁছে যাবে। আমাদের MainActivity ক্লাস থেকে কিভাবে ডেটা চেয়ে কল দেয়া হচ্ছে আর কিভাবে ডেটা রিসিভ করা হচ্ছে সেটা এখন দেখবো।
MainActivity class
public class MainActivity extends AppCompatActivity { private EditText userIdEditText; private EditText passwordEditText; private EditText jokeUserIdEditText; private TextView jokeTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Logger.addLogAdapter(new AndroidLogAdapter()); //Initialize the view like EditText, TextView userIdEditText = (EditText) findViewById(R.id.login_id); passwordEditText = (EditText) findViewById(R.id.login_password); jokeUserIdEditText = (EditText) findViewById(R.id.user_id_for_joke); jokeTextView = (TextView) findViewById(R.id.jokeTextView); } //button click event public void buttonClickEvent(View view){ //login button clicked if(view.getId()==R.id.login_button){ String userId; String password; User user = new User(); userId = userIdEditText.getText().toString(); password = passwordEditText.getText().toString(); user.setUserId(userId); user.setPassword(password); //call method of interface MyApiService myApiService = new NetworkCall(); myApiService.userValidityCheck(user, new ResponseCallback<String>() { @Override public void onSuccess(String msg) { showToast(msg); Logger.d("Activity: onSuccess of userValidity method is called"); } @Override public void onError(Throwable th) { showToast(th.getMessage()); Logger.d("Activity: onError of userValidity method is called"); } }); //user credential and listener } else { //get joke button clicked String userId; userId = jokeUserIdEditText.getText().toString(); //call method of interface MyApiService myApiService = new NetworkCall(); myApiService.getJokeFromServer(userId, new ResponseCallback<String>() { @Override public void onSuccess(String joke) { jokeTextView.setText(joke); Logger.d("Activity: onSuccess of getJoke method is called"); } @Override public void onError(Throwable th) { showToast(th.getMessage()); Logger.d("Activity: onError of getJoke method is called"); } }); //user credential and listener } } private void showToast(String msg) { Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); } }
button click এর ইভেন্টে MyApiService ইন্টারফেসের মেথডে কল করে ডেটা চাওয়া হয়েছে। ডেটা যখন রেডি হয়ে যাবে তখন ঐ মেথড কলের onSuccess() মেথড কল হবে। আর যদি ফেইল করে তাহলে orError() মেথডটা কল হবে। এরপর আমরা আমাদের ডেটা নিয়ে Toast-এ দেখানো বা TextView তে প্রিন্ট করে শো করতে পারি।
এরই মধ্যমে আমাদের different network layer implementation শেষ হলো।
পুরো ব্যাপারটা অনেকখানি হচ্ছে imagination আর visualization এর। সামনা-সামনি বসে কোড করে দেখিয়ে দিলে খুব সহজে বুঝে ফেলা যাবে। কিন্তু একটা নির্দিষ্ট ফরমেটে লিখে বিষয়টা বুঝানো একটু কঠিন। এটা কেন দরকার সেটা বুঝানোর জন্য Abstraction এর একটা বিস্তারিত পার্ট লিখতে হয়েছে। যাতে করে লেখার সাইজ আরো বেড়ে গেছে। ভালো হবে যদি গিট থেকে পুরো প্রোজেক্টটা নামিয়ে রান করে দেখেন। বুঝার সুবিধার্থে আমি Pretty logger library ইউজ করেছি। Android Monitor এ তাই সুন্দর ভাবে লগ প্রিন্ট হবে। সেটা দেখে কাজের ফ্লো অনেকটা ক্লিয়ার হবে আশা করি।
এই পোস্টের সম্পূর্ণ সোর্সকোড পাওয়া যাবে আমার গিটহাব রিপোজিটরিতে। এটা প্রথম পোস্টেরই রিপোজিটরি। নতুন একটা ব্রাঞ্চ খুলে সেখানে নতুন সোর্সকোডগুলো দেয়া হয়েছে। উল্লেখিত লিংকটি ঐ ব্রাঞ্চেরই লিংক। ঐ লিংকে গিয়ে ক্লোন বা ডাউনলোড করলেই নতুন সোর্সকোডগুলো পাওয়া যাবে।
কোথাও কোনো ভুল চোখে পড়লে বা কোনো পরামর্শ থাকলে কাইন্ডলি কমেন্টে জানাবেন। অথবা কোথাও বুঝতে অসুবিধা হলে সেটাও জানাবেন। আমি চেষ্টা করব ব্যাখ্যা করবার। ধন্যবাদ।
Thanks .
You are most welcome
Android Retrofit CRUD Application Post korle valo hoy…
HTTP verb – GET ও POST এর জন্য Retrofit এর ইন্টারফেসে যেভাবে @GET, @POST ইউজ করা হয়েছে। একই রকম ভাবে @PATCH, @PUT, @DELETE ব্যবহার করতে পারবেন। আপনার ডেটাবেজের CRUD অপারেশন কিভাবে কাজ করতে তার সাথে রেট্রোফিটের সম্পর্ক নাই।
Bhai Retrofit use kore ekta choto project describe kora jabe? Apnar post er moto bangla te kono android retrofit shomporke paini…
পোস্টের শেষে আমার গিটহাব রিপোজিটরির লিংক দেয়া আছে। সেখানে PHP ও Android উভয়ের ফুল প্রোজেক্ট পাবেন।
Apnake boss onk onk thanks. Really best article apnar ay site er golo… sorry for banglish 🙁
Vaiya Dependency Injection er sathe retrofit er upor ekta tutorial doc korle amader jnno onek help hoto. jazakallahu khairan
Vai apni to ekta api er jnno dekhailen. multiple api er khettre ki korbo?
একটা API আর multiple API বলতে কী বুঝাচ্ছেন বুঝতে পারছি না। এই প্রোজেক্টে দুইটা endpoint এ হিট করার কোড দেখানো হয়েছে। যার একটা GET ও অপরটি POST request.