آموزش پیاده سازی الگوی تبادل اطلاعات در Nested Fragment ها
در این آموزش قصد داریم تا یکی از مشکلات دیگری که در رابطه ی کار با Fragmentها وجود دارد را توضیح و راه حل آن را بررسی کنیم.
هنگام کار با Fragmentها یکی از دغدغه های برنامه نویسان نحوه تبادل اطلاعات بین Fragmentها و مخصوصا Nested Fragmentها (Fragment های تو در تو) (یعنی صدا زدن Fragment از داخل Fragment دیگری)
در آموزش قبلی نحوه تبادل اطلاعات از FragmentA به FragmentB رو توضیح دادیم. که میتونید از لینک زیر اون رو مطالعه کنید
ی اگر شما در حالت عادی تنها یک سطح Fragment داشته باشید روش توضیح داده شده در بالا بسیار کارامد هستش. اما زمانی که با Nested Fragment یا Fragmentهای تو در تو سروکار داشته باشید روش بالا دیگه جوابگو نیست.
به همین دلیل روش دیگری بر مبنای روش بالا طراحی شده که با هم به نحوه پیاده سازی اون میپردازیم.
برای آموزش این روش از یک سناریو انتقال اطلاعات استفاده میکنیم که ممکنه برای خیلی از دوستان سوال باشه ، که میتونن با مطالعه به جواب خودشون برسن.
سناریو: انتقال اطلاعات از StepOneFragment (که یک Nested Fragment در سطح 2) هستش به DialogFragment و بلعکس
Nested Fragment در سطح 2 یعنی StepOneFragment از داخل یک Fragment دیگه صدا زده شده یا Inflate شده.
حالا ما میخوایم که با فراخونی یک DialogFragment اطلاعاتی رو به اون Dialog ارسال و اطلاعاتی رو ازش دریافت و به StepOneFragment ارسال کنیم.
برای این کار ما از یک Interface بعنوان رابط برای تبادل اطلاعات بین Fragmentها استفاده میکنیم
DialogFragementResult.xml
کد پیاچپی:
public interface DialogFragementResult {
void onDialogFragmentResult(int TargetRequesCode, int Value);
}
این Interface همون رابط ما برای تبادل اطلاعات هستش که دارای یک تابع به همراه دو پارامتر هستش.
این الگو در حالت استاندارد دارای یک پارامتر هستش. اما برای اینکه بتونیم از این روش به دفعات زیاد و برای تمامی View های موجود در یک Fragment یا layout استفاده بشه از دو پارامتر استفاده شده که پارامتر اول تعیین کننده اون View هستش که باید اطلاعات بهش منتقل بشه و پارامتر دوم مقدار مورد نظر هستش
برای مثال ما نیاز داریم تا مقداری برگشتی از Dialog خودمون رو در EditText1 وارد کنیم ، برای همین منظور میایم و تمامی View های موجود در Layout رو شمار گذاری یا علامت گزاری میکنیم تا خودمون بدونم منظورمون با کدوم View هستش. مثلا من برای EditText1 عدد 1 رو در نظر گرفتم. میتونید شما 100 یا 999 یا هر چیزی که دوست دارید انتخاب کنید.
کلاس StepOneFragment
کد پیاچپی:
public class StepOneFragment extends Fragment implements DialogFragementResult {
TextView tv1;
Button btnshow;
View mRootView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
mRootView = inflater.inflate(R.layout.fragment_step_one, container, false);
tv1 = (EditText) mRootView.findViewById(R.id.textview1);
btnshow = (Button) mRootView.findViewById(R.id.btnshow);
btnshow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
Custom_Dialog dialog = Custom_Dialog();
dialog.setTargetFragment(new StepOneFragment(),1);
Bundle args = new Bundle();
args.putInt("Caller",1);
args.putInt("DefaultValue",2525);
dialog.setArguments(args);
dialog.show(fragmentManager,"DialogFragment");
}
});
return mRootView;
}
@Override
public void onDialogFragmentResult(int TargetRequesCode, int Value) {
if (TargetRequesCode == 1) {
if (Value != -1 ) {
tv1.setText(String.valueOf(Value));
}
}
}
}
این هم کلاس Fragment والد یا فراخواننده ما هستش ، که Dialog ما رو صدا زده و نتیجه Dialog باید به این Fragment ارجاع داده بشه.
قسمت های مهم این کلاس رو توضیح میدیم.
ما میخواهیم هنگامی که بر روی Button یا btnshow کلیک شد . پنجره Dialog ما باز بشه و یک مقداری به EditText یا EditText2 داخلی اون ارسال بشه.
کد پیاچپی:
FragmentManager fragmentManager = getFragmentManager();
Custom_Dialog dialog = Custom_Dialog();
dialog.setTargetFragment(new StepOneFragment(),1);
Bundle args = new Bundle();
args.putInt("Caller",1);
args.putInt("DefaultValue",2525);
dialog.setArguments(args);
dialog.show(fragmentManager,"DialogFragment");
ما برای انتقال اطلاعات از والد یا فراخواننده از Bundle استفاده می کنیم و بعنوان arguments اون رو به Dialog پاس میدیم.
با استفاده از تابع setArgument موجود در Dialog ( البته موجود در Dialog نمونه سازی شده نه خود کلاس اصلی Dialog )
برای این که Dialog ما بدونه والدش کیه و از طرف کی صدا زده شده. تا بعدا بتونه جواب یا نتیجه رو به اون ارجاع بده باید قبل از صدا زدنش بهش بگیم که والدش کیه . برای همین منظور از تابع setTargetFragment موجود در Dialog نمونه سازی شده استفاده میکنیم دو 2 مقدا رو بهش میدیم.
مقدار اول همون والد هستش و مقدار دوم یک عدد یکتا برای هر Instance یا نمونه متفاوت از Dialog هستش.
یعنی اگر نیاز شد چندین Dialog صدا زده بشن یا در هم باز بشن بشه اونها رو از هم تفکیک کرد.
و در اخر هم با فراخوانی تابع show مربوط به dialog میتونیم dialog مورد نظر رو نمایش بدیم.
تابع show دو پارامتر میگیره. پارامتر اول یک نمونه از FragmentManager و پارامتر دوم یک اسم یا مشخصه یکتا برای این Dialog در بخش FragmentManager هستش .
بعد از اون میریم به تابع پیاده سازی شده DialogFragmentResult که باید توسط این Fragement. پیاده سازی یا Implemnt بشه
کد پیاچپی:
@Override
public void onDialogFragmentResult(int TargetRequesCode, int Value) {
if (TargetRequesCode == 1) {
if (Value != -1 ) {
tv1.setText(String.valueOf(Value));
}
}
}
همانطور که میبینید دو پارامتر به این تابع ارسال میشه که پارامتر اولی برای تعیین کردن View ای که باید مقدار برگشتی تابع به اون داده بشه و پارامتر دوم هم مقدار برگشتی از dialog هستش .
در قسمت if(values !=-1) دلخواه هستش و ما میایم چک میکنیم که اگر کاربر گزینه انصراف یا همون نزده بود مقدار برگشتی رو به View بدیم. و اگر انصراف رو شده بود هیچ کاری باهاش نداریم.
کلاس CustomDialog یا همون DialogFragment
کد پیاچپی:
public class Custom_Dialog extends DialogFragment implements View.OnClickListener {
EditText tv2;
String mValue;
int mCaller;
DialogFragementResult dialogResult;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.custom_dialog, container, false);
getDialog().setCancelable(true);
dialogResult = (DialogFragementResult)getTargetFragment() ;
mCaller = getArguments().getInt("Caller");
tv2= (EditText) rootView.findViewById(R.id.edittext2);
mValue = getArguments().getInt("MaxValue");
tv2.setText(mValue);
Button btnset = (Button) rootView.findViewById(R.id.btnset);
Button btncancel = (Button) rootView.findViewById(R.id.btncancel);
btnset.setOnClickListener(this);
btncancel.setOnClickListener(this);
return rootView;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnset) {
if (Caller == 1) {
dialogResult.onDialogFragmentResult(1,mNumberPicker.getValue());
getDialog().dismiss();
}
}
if (v.getId() == R.id.btncancel) {
if (Caller == 1) {
getDialog().dismiss();
}
}
}
}
در کلاس customDialog طراحی شده میایم و یک نمونه از Interface یا همون رابط تبادل اطلاعات رو نمونه سازی میکنیم
کد پیاچپی:
DialogFragementResult dialogResult;
و در قسمت onCreateView اون رو مقدار دهی میکنیم.
اگر حواستون باشه در هنگام نمونه سازی و صدا زدن dialog با استفاده از تابع setTargetFragment به dialog گفتیم که والد صدا زننده شما کدوم Fragment هستش.
و حالا با استفاده از همون تابه یعنی getTargetFragment اون رو به نمونه رابط تبادل اطلاعات یا Interface DialogFragmentResult میدیم ولی با این تفاوت که هنگانم پر کردن نمونه Interface باید اون رو به DialogFragmentResutl تبدیل یا Cast کنیم. با این کار بهش میگیم برو و تابع پیاده سازی شده در Fragment والد یه پدر رو برای ما اجرا کن ( البته هنگامی که اون تابع رو صدا بزنیم و ازش استفاده کنیم. در اینجا فعلا فقط بهش معرفی کردیم)
مقادیری رو هم که هنگام نومنه سازی dialog با استفاده از کلاس bundle بهش داده بودیم . حالا با استفاده از تابع getArguments() میتونیم دریافتشون کنیم. دقیقا مثلا همون Bundle عمل می کنه یعنی با استفاده از متد های getInt و getString و ......
حالا نوبت به ارسال داده مورد نظر یا نتیجه از dialog به fragment والد میرسه.
برای راحتی کار من View.OnClickListener رو در CustomDialog پیاده سازی یا Implement کردم.
کد پیاچپی:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnset) {
dialogResult.onDialogFragmentResult(1,tv2.getText()());
getDialog().dismiss();
}
if (v.getId() == R.id.btncancel) {
getDialog().dismiss();
}
}
حالا میایم چک می کنیم که کدوم Button کلیک شده یعنی کلید تایید OK یا انصراف cancel
اگر کلید Ok زده شده بود میایم با استفاده از تابع نمونه سازی شده رابط DialogFragmentResult . تابع پیاده سازی شده در Fragment والد رو صدا میزنیم و مقادیر رو برای اون ارسال می کنیم.
کد پیاچپی:
dialogResult.onDialogFragmentResult(1,tv2.getText()());
و در نهایت dialog رو میبنیدم
کد پیاچپی:
getDialog().dismiss();
با مطالعه و دقت در این روش میتوان متوجه شد که از این روش در رابط با تبادل اطلاعات در Activity ها و Fragmentها و Nested Fragmentها میشه استفاده کرد.
البته روشهای دیگری هم وجود داره از جمعه shared preference یا Global Classes یا متغییرهای static که هر کدوم مزایا و معایب خاص خودشوم رو دارن.
روش گفته شده در بالا یکی از بهترین و استاندارد ترین روش تبادل اطلاعات هستش که در برنامه های حرفه ای از اون استفاده میشه و توصیه شده که از این روش بجای دیگر روشها استفاده کنید.
این روش کاملا تست شده و مشکلی در تبادل اطلاعات وجود نداره و اطلاعات یا داده ها از دومین Fragment به سومین Fragment و بلعکس ارجاع داده و دریافت شده
امیدوارم این آموزش مفید بوده باشه