পুনঃনির্মাণ 2.0 কীভাবে ডিসিরিয়ালেসড ত্রুটি পাওয়া যায় প্রতিক্রিয়া.বডি


128

আমি retrofit 2.0.0-beta1 ব্যবহার করছি ।

পরীক্ষাগুলিতে আমার একটি বিকল্প দৃশ্য আছে এবং HTTP 400 ত্রুটিটি আশা করি

আমি চাই retrofit.Response<MyError> response তবেresponse.body() == null

মাইআরআর ডিজিট্রাইজড নয় - আমি এটি কেবল এখানে দেখি

response.errorBody().string()

তবে এটি আমাকে বস্তু হিসাবে MyError দেয় না



ত্রুটির প্রতিক্রিয়া deserialize করা কি ভাল অনুশীলন? যেহেতু প্রতিক্রিয়াটি একটি ওয়েব সার্ভার ত্রুটি হতে পারে যা এইচটিএমএল।
হোসেইন শাহদুস্ট

1
thx @ আহমাদালিবালোচ, সেই লিঙ্কটি সত্যই সহায়ক।
রবি ভানিয়া

উত্তর:


138

আমি বর্তমানে খুব সহজ বাস্তবায়ন ব্যবহার করছি, যার জন্য রূপান্তরকারী বা বিশেষ ক্লাস ব্যবহার করার প্রয়োজন নেই। আমি যে কোডটি ব্যবহার করি তা নিম্নলিখিত:

public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    DialogHelper.dismiss();

    if (response.isSuccessful()) {
        // Do your success stuff...
    } else {
        try {
            JSONObject jObjError = new JSONObject(response.errorBody().string());
            Toast.makeText(getContext(), jObjError.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }
}

6
আপনার সমাধানটি ত্রুটির প্রতিক্রিয়া সামগ্রী দেখায় না।
কুলমাইন্ড

1
আমার সম্পাদনা পরীক্ষা করুন, কেন আমি এটিকে প্রথম স্থানে এতটা অস্পষ্ট করে দিয়েছি।
সাইফ Bechan

4
শেষ অবধি, কাজ করে এমন একটি অ্যান্ড্রয়েড প্রশ্নের একটি সাধারণ উত্তর (বেশিরভাগ অ্যান্ড্রয়েড উত্তর কমেন্টিকভাবে জটিল)।
ডগ ভোস

এটি অবশ্যই প্রশ্নের উত্তর দিচ্ছে না। এটি কেবল ত্রুটি বার্তা প্রদান করে ত্রুটি এনুম অবজেক্টটি নয়। : দয়া করে এই প্রতিক্রিয়া অনুসরণ stackoverflow.com/a/21103420/2914140
Tobliug

উত্তরে এটি "বার্তায়" ম্যাপিংয়ের সন্ধান করছে, তবে আমার ত্রুটির প্রতিক্রিয়াটি সেটিতে নেই। এটি "ত্রুটি" এ ম্যাপিং ছিল। সুতরাং সবাই পড়ছেন, এটি আপনার সাড়া পাওয়ার উপর নির্ভর করে!
এমকো

43

ত্রুটিআরস্পোন আপনার কাস্টম প্রতিক্রিয়া অবজেক্ট

Kotlin

val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
var errorResponse: ErrorResponse? = gson.fromJson(response.errorBody()!!.charStream(), type)

জাভা

Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);

3
আপনাকে বাধ্যতামূলকভাবে মোড়ানো বিকল্পগুলি করা উচিত নয়:gson.fromJson(response.errorBody()?.charStream(), type)
আশামান

37

আমি এর দ্বারা সমাধান করেছি:

 if(!response.isSuccessful()){
       Gson gson = new Gson();
       MyErrorMessage message=gson.fromJson(response.errorBody().charStream(),MyErrorMessage.class);
       if(message.getCode()==ErrorCode.DUPLICATE_EMAIL_ID_CODE){
                  //DO Error Code specific handling                        
        }else{
                 //DO GENERAL Error Code Specific handling                               
        }
    }

MyErrorMessage শ্রেণি:

  public class MyErrorMessage {
     private int code;
     private String message;

     public int getCode() {
        return code;
     }

     public void setCode(int code) {
        this.code = code;
     }

     public String getMessage() {
         return message;
     }

     public void setMessage(String message) {
        this.message = message;
     }
   }

2
java.lang.IllegalStateException: প্রত্যাশিত BEGIN_OBJECT তবে STRING এ ছিল 1 কলাম 2 পথে লাইন $
রোনেল গঞ্জেলস

.addConverterFactory(ScalarsConverterFactory.create())@ রোনেলগনজালেস ব্যবহার করুন
প্রতীক

30

রেট্রোফিট ২.০ বিটা ২-তে এইভাবে আমি ত্রুটির প্রতিক্রিয়া পাচ্ছি:

  1. সমলয়

    try {
       Call<RegistrationResponse> call = backendServiceApi.register(data.in.account, data.in.password,
               data.in.email);
       Response<RegistrationResponse> response = call.execute();
       if (response != null && !response.isSuccess() && response.errorBody() != null) {
           Converter<ResponseBody, BasicResponse> errorConverter =
                   MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
           BasicResponse error = errorConverter.convert(response.errorBody());
           //DO ERROR HANDLING HERE
           return;
       }
       RegistrationResponse registrationResponse = response.body();
       //DO SUCCESS HANDLING HERE
    } catch (IOException e) {
       //DO NETWORK ERROR HANDLING HERE
    }
  2. অসমনিয়ত

    Call<BasicResponse> call = service.loadRepo();
    call.enqueue(new Callback<BasicResponse>() {
        @Override
        public void onResponse(Response<BasicResponse> response, Retrofit retrofit) {
            if (response != null && !response.isSuccess() && response.errorBody() != null) {
                Converter<ResponseBody, BasicResponse> errorConverter =
                    retrofit.responseConverter(BasicResponse.class, new Annotation[0]);
                BasicResponse error = errorConverter.convert(response.errorBody());
                //DO ERROR HANDLING HERE
                return;
            }
            RegistrationResponse registrationResponse = response.body();
            //DO SUCCESS HANDLING HERE
        }
    
        @Override
        public void onFailure(Throwable t) {
            //DO NETWORK ERROR HANDLING HERE
        }
    });

Retrofit 2 বিটা 3 এর জন্য আপডেট করুন:

  1. সিঙ্ক্রোনাস - পরিবর্তিত হয়নি
  2. অ্যাসিঙ্ক্রোনাস - রেসপন্স থেকে retrofit প্যারামিটার সরানো হয়েছে

    Call<BasicResponse> call = service.loadRepo();
    call.enqueue(new Callback<BasicResponse>() {
        @Override
        public void onResponse(Response<BasicResponse> response) {
            if (response != null && !response.isSuccess() && response.errorBody() != null) {
                Converter<ResponseBody, BasicResponse> errorConverter =
                    MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
                BasicResponse error = errorConverter.convert(response.errorBody());
                //DO ERROR HANDLING HERE
                return;
            }
            RegistrationResponse registrationResponse = response.body();
            //DO SUCCESS HANDLING HERE
        }
    
        @Override
        public void onFailure(Throwable t) {
            //DO NETWORK ERROR HANDLING HERE
        }
    });

4
BasicResponse এ আপনার কী আছে?
জেমশিট ইস্কেন্দ্রভ

2
ম্যাসেজ এবং ত্রুটি কোড সহ কেবল একটি বেসিক জ্যাকসন টীকা প্রাপ্ত ক্লাস। যে কোনও ক্ষেত্রে আপনার যে কোনও এনোটোটেড ক্লাস থাকতে পারে যা আপনার সার্ভারের প্রতিক্রিয়ার ধরণের সাথে মেলে। আপনার প্রয়োজনের সাথে মিলে যায় এমন একটি উত্পন্ন করতে jsonschema2pojo ব্যবহার করার চেষ্টা করুন ।
জেফ্রিমান

অন্যদের জন্য, আপনি পরিবর্তে এটি ব্যবহার করতে পারেন: রূপান্তরকারী <রেসপন্সবিডি, <ম্যাসেজ> ত্রুটি রূপান্তরকারী = retrofit.responseBodyConverter (বার্তা.ক্লাস, নতুন টিকা [0]);
কিম মন্টানো

@ জেফ্রিমন, আমি যদি ডিসরিয়ালাইজ করতে চাই List<BasicResponse>?
আজিজবিকিয়ান

আপনি কি আমার জন্য ক্লাস মাই অ্যাপ্লিকেশনটি দেখতে পাবেন
dungtv

10

ইন https://stackoverflow.com/a/21103420/2914140 এবং https://futurestud.io/tutorials/retrofit-2-simple-error-handling এই বৈকল্পিক রেট্রোফিট 2.1.0 জন্য প্রদর্শিত হয়।

call.enqueue(new Callback<MyResponse>() {
    @Override
    public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
        if (response.isSuccessful()) {
            ...
        } else {
            Converter<ResponseBody, MyError> converter
                    = MyApplication.getRetrofit().responseBodyConverter(
                    MyError.class, new Annotation[0]);
            MyError errorResponse = null;
            try {
                errorResponse = converter.convert(response.errorBody());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

9
 @Override
 public void onResponse(Call<Void> call, retrofit2.Response<Void> response) {
            if (response.isSuccessful()) {

            //Do something if response is ok
            } else {

                JsonParser parser = new JsonParser();
                JsonElement mJson = null;
                try {
                    mJson = parser.parse(response.errorBody().string());
                    Gson gson = new Gson();
                    MyError errorResponse = gson.fromJson(mJson, MyError.class);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

            }

9

ত্রুটি প্রতিক্রিয়া এবং এর প্রতিক্রিয়াটিকে রূপান্তর করতে ব্যবহারকারী গসনের একটি মডেল তৈরি করুন। এটি ঠিক কাজ করবে।

APIError.java

public class APIError {
    private String message;

    public String getMessage() {
        return message;
    }
}

MainActivity.java (অভ্যন্তরীণ অনুরোধ onResponse)

if (response.isSuccessful()) {
    // Do your success stuff...

} else {
    APIError message = new Gson().fromJson(response.errorBody().charStream(), APIError.class);
    Toast.makeText(MainActivity.this, "" + message.getMessage(), Toast.LENGTH_SHORT).show();
}

7

আমি এহেনক্রোনাস কলগুলির জন্য এইভাবে রেট্রোফিট ২.০-বিটা 2 ব্যবহার করে করেছি:

@Override
public void onResponse(Response<RegistrationResponse> response, 
                       Retrofit retrofit) {
    if (response.isSuccess()) {
        // Do success handling here
    } else {
        try {
            MyError myError = (MyError)retrofit.responseConverter(
                    MyError.class, MyError.class.getAnnotations())
                .convert(response.errorBody());
            // Do error handling here
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

মাই ইয়ারার ক্লাসটি কী হবে?
ধ্রুপাল

আমি ভেবেছিলাম রিসপন্সে একটি কল প্যারামিটার এবং একটি প্রতিক্রিয়া প্যারামিটার থাকার কথা। আপনার কীভাবে এটি একটি retrofit পরামিতি আছে?
মার্টি মিলার

@ মার্টিমিলার এটি নীচের সংস্করণটির জন্য করা হয়েছিল retrofit retrofit 2.0-beta2
শান্তানু

7

আপনি যদি কোটলিন ব্যবহার করেন তবে অন্য সমাধানটি হতে পারে প্রতিক্রিয়া শ্রেণীর জন্য কেবল এক্সটেনশন ফাংশন তৈরি করা:

inline fun <reified T>Response<*>.parseErrJsonResponse(): T?
{
    val moshi = MyCustomMoshiBuilder().build()
    val parser = moshi.adapter(T::class.java)
    val response = errorBody()?.string()
    if(response != null)
        try {
            return parser.fromJson(response)
        } catch(e: JsonDataException) {
            e.printStackTrace()
        }
    return null
}

ব্যবহার

val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
   // handle your error logic here
   // ...
}

অবশেষে কেউ কোডল পড়া সহজ করতে কোটলিন শক্তি ব্যবহার করেছে!
ভিন্স

5

এটি আসলে খুব সোজা এগিয়ে।

Kotlin:

val jsonObj = JSONObject(response.errorBody()!!.charStream().readText())
responseInterface.onFailure(jsonObj.getString("msg"))

জাভা:

JSONObject jsonObj = new JSONObject(response.errorBody().charStream().readText());
responseInterface.onFailure(jsonObj.getString("msg"));

Retrofit: 2.5.0 এ পরীক্ষিত 2.5 চার স্ট্রিমের পাঠ্যটি পড়ুন যা আপনাকে একটি স্ট্রিং দেবে, তারপরে JSONObject এ পার্স করবে।

Adios থেকে।


readText()জাভাতে কোনও এক্সটেনশন নেই , TextStreamsKt.readText(response.errorBody().charStream())আপনি এখনও জাভাতে থাকলে ব্যবহার করুন
মোচাদ্বী

4

আমি একই সমস্যা ছিল। আমি এটি প্রত্যাহার করে সমাধান করেছি। আমাকে এটি দেখাতে দাও ...

আপনার ত্রুটি যদি JSON কাঠামো মত হয়

{
"error": {
    "status": "The email field is required."
}
}


My ErrorRespnce.java 

public class ErrorResponse {

   @SerializedName("error")
   @Expose
   private ErrorStatus error;

   public ErrorStatus getError() {
      return error;
   }

   public void setError(ErrorStatus error) {
      this.error = error;
   }
}

এবং এটি আমার ত্রুটি স্থিতির ক্লাস

public class ErrorStatus {

  @SerializedName("status")
  @Expose
  private String status;

  public String getStatus() {
      return status;
  }

  public void setStatus(String status) {
      this.status = status;
  }
}

এখন আমাদের একটি ক্লাস দরকার যা আমাদের জসন পরিচালনা করতে পারে।

  public class ErrorUtils {

   public static ErrorResponse parseError (Response<?> response){
      Converter<ResponseBody , ErrorResponse> converter =          ApiClient.getClient().responseBodyConverter(ErrorResponse.class , new Annotation[0]);
    ErrorResponse errorResponse;
    try{
        errorResponse = converter.convert(response.errorBody());
    }catch (IOException e){
        return new ErrorResponse();
    }
    return errorResponse;
}
}

এখন আমরা retrofit এপিআই কলটিতে আমাদের প্রতিক্রিয়া পরীক্ষা করতে পারি

private void registrationRequest(String name , String email , String password , String c_password){


    final Call<RegistrationResponce> registrationResponceCall = apiInterface.getRegistration(name , email , password , c_password);
    registrationResponceCall.enqueue(new Callback<RegistrationResponce>() {
        @Override
        public void onResponse(Call<RegistrationResponce> call, Response<RegistrationResponce> response) {



            if (response.code() == 200){


            }else if (response.code() == 401){


                ErrorResponse errorResponse = ErrorUtils.parseError(response);
                Toast.makeText(MainActivity.this, ""+errorResponse.getError().getStatus(), Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onFailure(Call<RegistrationResponce> call, Throwable t) {

        }
    });
}

এটি এখন আপনি নিজের টোস্টটি দেখাতে পারেন


4

Kotlinএক্সটেনশনগুলি ব্যবহার করে এখানে মার্জিত সমাধান রয়েছে :

data class ApiError(val code: Int, val message: String?) {
    companion object {
        val EMPTY_API_ERROR = ApiError(-1, null)
    }
}

fun Throwable.getApiError(): ApiError? {
    if (this is HttpException) {
        try {
            val errorJsonString = this.response()?.errorBody()?.string()
            return Gson().fromJson(errorJsonString, ApiError::class.java)
        } catch (exception: Exception) {
            // Ignore
        }
    }
    return EMPTY_API_ERROR
}

এবং ব্যবহার:

showError(retrofitThrowable.getApiError()?.message)


3

আপনি কেবল রেট্রোফিট থেকে তৈরি কোনও পরিষেবা ইনজেকশন দিলে এইভাবে আপনাকে পুনঃনির্মাণের উদাহরণের প্রয়োজন হবে না।

public class ErrorUtils {

  public static APIError parseError(Context context, Response<?> response) {

    APIError error = new APIError();

    try {
        Gson gson = new Gson();
        error = gson.fromJson(response.errorBody().charStream(), APIError.class);
    } catch (Exception e) {
        Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
    }

    if (TextUtils.isEmpty(error.getErrorMessage())) {
        error.setError(response.raw().message());
    }
    return error;
  }
}

এটি এর মতো ব্যবহার করুন:

if (response.isSuccessful()) {

      ...

    } else {

      String msg = ErrorUtils.parseError(fragment.getActivity(), response).getError(); // would be from your error class
      Snackbar.make(someview, msg, Snackbar.LENGTH_LONG).show();
    }
  }

2

আপনি যখন রেট্রোফিটের সাথে OkHttp ব্যবহার করেন তখন এটি সমস্যা বলে মনে হচ্ছে, তাই আপনি হয় ত্রুটিটি পেতে OkHttp সরিয়ে ফেলতে পারেন বা নীচের কোডটি ব্যবহার করতে পারেন:

if (!response.isSuccessful()) {
 InputStream i = response.errorBody().byteStream();
 BufferedReader r = new BufferedReader(new InputStreamReader(i));
 StringBuilder errorResult = new StringBuilder();
 String line;
 try {
   while ((line = r.readLine()) != null) {
   errorResult.append(line).append('\n');
   }
 } catch (IOException e) { 
    e.printStackTrace(); 
}
}

2

একটি স্ট্রিং এ ত্রুটিবডি পড়ুন এবং ম্যানুয়ালি জেসসনকে পার্স করুন।

 if(!response.isSuccessful()) {
   
    String error = "";
    try {
        BufferedReader ereader = new BufferedReader(new InputStreamReader(
                response.errorBody().byteStream()));
        String eline = null;
        while ((eline = ereader.readLine()) != null) {
            error += eline + "";
        }
        ereader.close();
    } catch (Exception e) {
        error += e.getMessage();
    }
    Log.e("Error",error);
    
    try {
        JSONObject reader = new JSONObject(error);
        String message = reader.getString("message");
    
        Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
    
    } catch (JSONException e) {
        e.printStackTrace();
    }
 }

0

এটি দ্বারা সমাধান:

Converter<MyError> converter = 
    (Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError =  converter.fromBody(response.errorBody());

আমি কীভাবে জিসনকনভার্টারফ্যাক্টরির মাধ্যমে রূপান্তর করতে পারি? কোন ধারণা ?
শান Xeeshi

আমি একটি উপায় খুঁজে পেয়েছি। আমি শুধু পরিবর্তন JacksonConverterFactory করতে GsonConverterFactoryএটা আমার নিজস্ব বস্তুর মধ্যে JSON পরিবর্তন করে কিন্তু এটি সতর্কবার্তা দেয় টিক চিহ্ন সরিয়ে দিলে ঢালাই retrofit.Converter <ক্যাপচার <>>?
শান Xeeshi

3
MyError ক্লাস কি আছে?
ধ্রুপাল

0
try{
                ResponseBody response = ((HttpException) t).response().errorBody();
                JSONObject json = new JSONObject( new String(response.bytes()) );
                errMsg = json.getString("message");
            }catch(JSONException e){
                return t.getMessage();
            }
            catch(IOException e){
                return t.getMessage();
            }

0

কোটলিনে:

val call = APIClient.getInstance().signIn(AuthRequestWrapper(AuthRequest("1234567890z", "12341234", "nonce")))
call.enqueue(object : Callback<AuthResponse> {
    override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
        if (response.isSuccessful) {

        } else {
            val a = object : Annotation{}
            val errorConverter = RentalGeekClient.getRetrofitInstance().responseBodyConverter<AuthFailureResponse>(AuthFailureResponse::class.java, arrayOf(a))
            val authFailureResponse = errorConverter.convert(response.errorBody())
        }
    }

    override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
    }
})

0

ত্রুটিযুক্ত মান মান retrofit এ APIError অবজেক্ট সেট করা উচিত। সুতরাং, আপনি নীচের কোড গঠন ব্যবহার করতে পারেন।

public class APIErrorUtils {

    public static APIError parseError(Response<?> response) {
        Converter<ResponseBody, APIError> converter = API.getClient().responseBodyConverter(APIError.class, new Annotation[0]);

        APIError error;

        try {
            error = converter.convert(response.errorBody());
            Log.d("SERVICELOG", "****************************************************");
            Log.d("SERVICELOG", "***** SERVICE LOG");
            Log.d("SERVICELOG", "***** TIMESTAMP: " + String.valueOf(error.getTimestamp()));
            Log.d("SERVICELOG", "***** STATUS: " + String.valueOf(error.getStatus()));
            Log.d("SERVICELOG", "***** ERROR: " + error.getError());
            Log.d("SERVICELOG", "***** MESSAGE: " + error.getMessage());
            Log.d("SERVICELOG", "***** PATH: " + error.getPath());
            Log.d("SERVICELOG", "****************************************************");
        } catch (IOException e) {
            return new APIError();
        }

        return error;
    }
}

APIError error = APIErrorUtils.parseError(response);
if (error.getStatus() == 400) {
    ....
}

0

পরীক্ষিত এবং কাজ করে

 public BaseModel parse(Response<BaseModel> response , Retrofit retrofit){
            BaseModel error = null;
            Converter<ResponseBody, BaseModel> errorConverter =
                    retrofit.responseBodyConverter(BaseModel.class, new Annotation[0]);
            try {
                if (response.errorBody() != null) {
                    error = errorConverter.convert(response.errorBody());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return error;
        }

-1
val error = JSONObject(callApi.errorBody()?.string() as String)
            CustomResult.OnError(CustomNotFoundError(userMessage = error["userMessage"] as String))

open class CustomError (
    val traceId: String? = null,
    val errorCode: String? = null,
    val systemMessage: String? = null,
    val userMessage: String? = null,
    val cause: Throwable? = null
)

open class ErrorThrowable(
    private val traceId: String? = null,
    private val errorCode: String? = null,
    private val systemMessage: String? = null,
    private val userMessage: String? = null,
    override val cause: Throwable? = null
) : Throwable(userMessage, cause) {
    fun toError(): CustomError = CustomError(traceId, errorCode, systemMessage, userMessage, cause)
}


class NetworkError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Usted no tiene conexión a internet, active los datos", cause)

class HttpError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage, cause)

class UnknownError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Unknown error", cause)

class CustomNotFoundError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
    CustomError(traceId, errorCode, systemMessage, userMessage?: "Data not found", cause)`
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.