چرخه حیات و ارتباطات فرگمنت

چرخه حیات فرگمنت و اکتیویتی و ارتباط آن‌ها
چرخه حیات فرگمنت و اکتیویتی و ارتباط آن‌ها

در این نوشتار می‌خواهم مفاهیم مربوط به چرخه حیات و ارتباطات فرگمنت را توضیح دهم، آنچه در این نوشتار خواهید آموخت:

  • درک چرخه حیات فرگمنت
  • کاربرد کال بک‌های چرخه حیات فرگمنت
  • ارتباطات بین فرگمنت و اکتیویتی
  • شناخت بک استک فرگمنت
  • ارسال داده بین فرگمنت و اکتیویتی

مقدمه :

خب اول بیایین ببینیم اصلا چرخه حیات چی هست؟لایف سایکل یا چرخه حیات، دوره‌های زندگی یک فرگمنت یا اکتیویتی ست که از هنگام تولد تا مرگ در اون دوره ها قرار می‌گیره و سیستم هم با استفاده از یک رخدادی به نام callBack بهمون خبر میده که الان فرگمنت یا اکتیویتی توی اون قسمت از چرخه حیات افتاده. ما هم با شناختن این callBack ها و اینکه کی فراخوانی میشن، میتونیم کارهای جالبی انجام بدیم. شکل بالا هم داره callBackهای چرخه حیات اکتیویتی و فرگمنت رو بهمون نشون می‌ده.

مثلاً وقتی اکتیویتی برای اول بار متولد می‌شه، سیستم، کال بک onCreate رو صدا میزنه. و وقتی زمان مرگ اکتیویتی برسه سیستم کال بک onDestroy را فراخوانی می کنه که دیگه آخرین تعاملمون رو با اکتیویتی داشته باشیم. مثلا اگر هنوز منابعی مونده که در کال بک‌های قبلی مثل "آن استاپ" آزادشون نکردیم، اینجا آزادش کنیم. (متاسفانه نام بعضی از توابع در این بستر حذف می‌شود و من مجبورم به فارسی آن‌ها را بنویسم)

هدف این مقاله:

وقتی ما به عنوان یک برنامه‌نویس کال بک‌های چرخه حیات رو درست نشناسیم باعث میشه از مزیت‌ آن‌ها بی‌بهره بمونیم یا حتی گاهی چیزهایی رو درست مدیریت نکنیم.

مانند اکتیویتی، فرگمنت هم چرخه حیات خودشو داره. اگر ارتباط بین چرخه حیات فرگمنت و اکتیویتی رو درک کنید، اون موقع می‌تونید فرگمنت‌هایی طراحی کنید که متغیرها رو ذخیره و بازیابی کنند و با اکتیویتی‌ها ارتباط برقرار کنند.

یک اکتیویتی که میزبان یک فرگمنت هست، میتونه اطلاعات رو به فرگمنت ارسال کنه و اطلاعات رو از فرگمنت دریافت کنه. در این نوشتار، فرآیندهایی برای ارسال داده از فرگمنت به اکتیویتی و بلعکس رو یاد می‌گیرید و اینکه چطور چرخه حیات فرگمنت رو مدیریت کنیم، توضیح داده خواهد شد.

درک چرخه حیات فرگمنت

استفاده از چرخه حیات فرگمنت خیلی شبیه استفاده از چرخه حیات اکتیویتی ست. در callbackهای چرخه حیاتِ فرگمنت می‌تونیم تعیین کنیم که فرگمنتمون در یک وضعیت مشخص چه رفتاری داشته باشه؟ وضعیت هایی مثل Active، مثل paused یا stopped.

وضعیت‌های چرخه حیات فرگمنت

فرگمنت توسط اکتیویتی میزبان‌اش add می‌شود. بعد از اینکه فرگمنت add شد، در طول چرخه حیاتش می‌تونه سه وضعیت زیر رو داشته باشه:

  • اکتیو یا رزیوم (active یا resume)
  • پاز شده (paused)
  • استاپ شده (stopped)
وضعیت‌های فرگمنت در طول چرخه حیات
وضعیت‌های فرگمنت در طول چرخه حیات

چطور وضعیت اکتیویتی روی فرگمنت تاثیر می‌گذارد؟

از جایی‌که یک فرگمنت همیشه مهمون یک اکتیویتی هست، چرخه حیات فرگمنت مستقیماً از چرخه حیات میزبانش یعنی اکتیویتی تاثیر می‌پذیرد. مثلاً وقتی یک اکتیویتی پاز می‎شود، تمام فرگمنت‌هایی که توش هستند هم پاز می‌شوند و وقتی اکتیویتی destroy می‌شود، همه فرگمنتهاشم نابود می‌شوند.

هر callbackای که در چرخه حیات اکتیویتی فراخوانی می‌شه در callback مشابه‌اش در فرگمنت هم اثر می‌گذارد. مثلاً وقتی یک اکتیویتی "آن پاز" رو می‌گیره، متد "آن پاز" برای هر فرگمنت این اکتیویتی هم فراخوانی می‌شه. برای فهم بیشتر جدول زیر رو ببینید:

همانند اکتیویتی، variable assignments را می‌توانید در فرگمنت هم ذخیره کنید. از آنجا که داده‌های فرگمنت معمولا به اکتیویتی میزبانش مربوط هستند، کد اکتیویتی میتونه از یک callback برای بازیابی داده از فرگمنت استفاده کنه و بعد از دوباره ساخته شدن فرگمنت هم، داده ها رو بازیابی کنه. که در ادامه راجع به ارتباط بین اکتیویتی و فرگمنت بیشتر خواهید خواند.

کاربرد callback های چرخه حیات فرگمنت

مانند کاری که با متدهای چرخه حیات اکتیویتی می‌تونستید انجام بدید، در فرگمنت هم می‌تونید متدهای چرخه حیاتش رو override کنید و وقتی فرگمنت در وضعیت‌های معینی قرار می‌گیره، کارهای مهمی رو انجام بدهیم. پر کاربردترین کال بک هایی که معمولا override میشن در زیر اومدن:

  • متد ()onCreate: کامپوننت‌های مهم و متغیرهای فرگمنت را در این تابع مقداردهی اولیه کنید، چونکه وقتی اکتیویتی ساخته می‌شود این متد فراخوانی میشه. هرچیزی که در این متد، مقداردهی اولیه شود، اگر فرگمنت پاز و رزیوم شود، محفوظ خواهد ماند.
  • متد ()onCreateView: لایه XML فرگمنت در این متد inflate می‌شود (یعنی اندروید با ایجاد آبجکت های ویو در حافظه، لایه XML را رندر می‌کند.). سیستم در این متد برای اولین بار ظاهر فرگمنت را ترسیم می‌کند و فرگمنت در اکتیویتی قابل دیدن می‌شه. برای ترسیم شدن ظاهر فرگمنت، شما باید ویو اصلی یا همون ویو روتِ لایه فرگمنت رو return کنید. اگر هم فرگمنتتون UI نداره باید null رو return کنید.
  • متد "آن پاز" : اگر می‌خواهید هر داده و وضعیتی در طول تخریب فرگمنت در امان بمونه در این متد ذخیره کنید. این متد در شرایط زیر توسط سیستم صدا زده میشه:

1. کاربر back بزنه.

2. فرگمنت replace یا removed بشه یا هر عملیات دیگه‌ای که فرگمنت رو تغییر بده.

3. اکتیویتی میزبان پاز بشه.

یک فرگمنت پاز شده، فرگمنتیه که هنوز زنده است! (همه اطلاعات member ها و state ها توسط سیستم حفظ می‌شود) و اگر کاربر دکمه بک را بزند و فرگمنت از بک استک برگردد، آنگاه چرخه حیات فرگمنتی که پاز شده بود با فراخوانی متد ()onCreateView از سر گرفته می‌شود. . اما اگر اکتیویتی destroy بشه(از بین بره)، فرگمنت هم از بین خواهد رفت.

فرگمنت Callback‌های کاربردی دیگر مانند موارد زیر هم دارد:

  • متد ()onAttach: وقتی فرگمنت به اکتیویتی میزبان افزوده می‌شود، فراخوانی می‌شود. در این متد می‌توانید چک کنید که اکتیویتی اینترفیس تعریف شده در فرگمنت را پیاده‌سازی کرده یا خیر.(مثالی از این مورد را در ادامه خواهید دید) بعد از این متد ()onCreate فراخوانی می‌شود.(در بالا تعریف شد)
  • متد "آن رزیوم" : اکتیویتی این متد را فراخوانی می‌کنه تا فرگمنتی که فعال و قابل مشاهده ست را از سرگیری کند.
  • متد ()onActivityCreated: وقتی متد onCreate اکتیوتی تمام می‌شود، این کال بک فراخوانی میشه و بهمون اطلاع میده که اکتیویتی ساخته شده ست. از این متد برای مقداردهی های اولیه‌ی نهایی استفاده کنید، مانند بازیابی viewها و stateها. همچنین این متد برای فرگمنت‌هایی که از ()setRetainInstance برای حفظ instance خود می‌کنند، مفید است، چون وقتی این متد اجرا شود به فرگمنت می‌گوید که چه وقت با instance اکتیویتی جدید مرتبط شده است. این متد بعد از ()onCreateView و قبل از ()onViewStateRestored فراخوانی می‌شود.
  • متد ()onDestroyView: وقتی viewای که توسط ()onCreateView ساخته شده بود از فرگمنت حذف می‌شود، فراخوانی خواهد شد. این متد وقتی اکتیویتی میزبان استاپ می‌شود یا فرگمنت را حذف می‌کند، نیز فراخوانی می‌شود. از این متد برای انجام کارهایی مثل log کردن یک پیام استفاده کنید و توجه کنید که فرگمنت دیگر قابل مشاهده نیست و بار بعدی که فرگمنت بخواد نمایش داده بشه یک view جدید ساخته می‌شه. این متد بعد "آن استاپ" از و قبل از ()onDestroy فراخوانی میشه.

ارتباطات فرگمنت و اکتیویتی

اکتیویتی می‌تونه با گرفتن یک رفرنس از فرگمنت، از متدهای فرگمنت استفاده کنه، و به همین ترتیب فرگمنت هم می‌تونه با گرفتن یک رفرنس اکتیویتی (getActivity) از ریسورس‌هاش، مثلاً از یک ویو استفاده کنه که در ادامه با مثال این دو مورد را خواهید آموخت.

استفاده از کانتکست اکتیویتی در یک فرگمنت

وقتی فرگمنت در وضعیت فعال یا resume هست، فرگمنت می تونه با ()getActivity یک رفرنس به instance اکتیویتی میزبان خودش بگیره. همچنین می‌تونه از این طریق ویوهای درون لایه اکتیویتی را find کند:

View listView = getActivity().findViewById(R.id.list);

توجه کنید اگر فرگمنت هنوز به اکتیویتی attach نشده باشد، ()getActivity نال برمی‌گرداند.

فراخوانی توابع فرگمنت در اکتیویتی میزبانش

به همین ترتیب، اکتیویتی شما می‌تونه رفرنس به فرگمنت رو از FragmentManager با استفاده از ()findFragmentById بدست بیاره. مثال زیر متد ()getSomeData فرگمنت رو فراخوانی می‌کنه:

ExampleFragment fragment = (ExampleFragment)
getFragmentManager().findFragmentById(R.id.example_fragment);
// ...
mData = fragment.getSomeData();

شناخت back stack فرگمنت

یک تفاوت مهم بین فرگمنت و اکتیویتی اینه که چطور اکتیویتی‌ها و فرگمنت‌ها از back stack خودشون استفاده می‌کنند. با شناخت بک استک فرگمنت می‌تونید بک زدن کاربر در فرگمنت رو مدیریت کنید.

  • در اکتیویتی، سیستم به طور خودکار back stack اکتیویتی‌ها رو نگه می‌داره.
  • در فرگمنت، اکتیویتیِ میزبان back stack رو نگه می‌داره و شما در طول هر عملیاتی که فرگمنت رو اضافه میکنه، مستقیماً با متد ()addToBackStack، فرگمنت رو باید به بک استک اضافه کنی. (یعنی خودکار نیست)

اینو تو ذهنتون داشته باشید که وقتی برنامه شما یک فرگمنت رو replace یا remove می‌کنه، کار مناسب اینه که این شانس رو به کاربر بدیم که بک بزنه و دوباره فرگمنت رو ببینه. برای اینکار باید متد ()addToBackStack رو قبل از کامیت کردنِ FragmentTransaction فراخوانی کنید.

fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

توجه: متد ()addToBackStack یک رشته ورودی اختیاری می‌گیره که یک نام یکتا برای تراکنش جاری هست. ما می‌تونیم به جای اون null ارسال کنیم، چون بهش نیازی نداریم، مگر اینکه بخوایم عملیات فرگمنتیه پیشرفته‌ای با استفاده از اینترفیس FragmentManager.BackStackEntry انجام بدیم.

هنگام حذف یک فرگمنت بخاطر داشته باشید که اگر از روش بالا استفاده کرده باشید، اکتیویتی میزبانش یک back stack برای فرگمنت نگه می‌داره. به این صورت، فرگمنت نابود نمیشه و اگر کاربر بک بزنه، ریستارت می‌شه.

ارسال داده بین یک فرگمنت و اکتیویتی

اکتیویتی که میزبان یک فرگمنته، می‌تونه اطلاعاتی رو براش بفرسته و اطلاعاتی رو ازش بگیره. و به این صورته که یک فرگمنت نمی‌تونه مستقیماً با یک فرگمنت دیگه ارتباط داشته باشه، همه ارتباطات فرگمنت به فرگمنت از طریق اکتیویتی میزبان اون‌ها انجام می‌شه. یعنی فرگمنت با اکتیویتی‌اش ارتباط برقرار می‌کنه و اکتیویتی با فرگمنت دیگه. در ادامه این ارتباط رو به روشنی توضیح خواهم داد.

ارتباط دو فرگمنت از طریق اکتیویتی میزبان آن‌ها
ارتباط دو فرگمنت از طریق اکتیویتی میزبان آن‌ها

ارسال داده از اکتیویتی به فرگمنت:

فرض کنید کاربر قبلاً در فرگمنت انتخابی انجام داده است و حالا شما می‌خواهید وقتی فرگمنت رو در اکتیویتی start می‌کنید این انتخاب رو برای فرگمنت ارسال کنید.

بهترین روش برای مقداردهی اولیه داده در یک فرگمنت، استفاده از متد فکتوری فرگمنت می‌باشد. همان طور که می‌دانید، می‌تونید instance فرگمنت رو با متد ()newInstance درون فرگمنت بسازید.

public static SimpleFragment newInstance() {
        return new SimpleFragment();
}

سپس با صدا زدن این تابع ()newInstance در اکتیویتی، از فرگمنت نمونه بسازید.

SimpleFragment fragment = SimpleFragment.newInstance();

برای مثال، اگر بخواهید فرگمنت رو با انتخاب قبلی کاربر باز کنید، در اکتیویتی مانند زیر عمل می‌کنید:

SimpleFragment fragment = SimpleFragment.newInstance(mRadioButtonChoice);

حالا فکتوری متد ()newInstance در فرگمنت می‌تونه از باندل و setArgument استفاده کنه تا قبل از اجرای فرگمنت، آرگومان هارو ست کنه.

public static SimpleFragment newInstance(int choice) {
        SimpleFragment fragment = new SimpleFragment();
        Bundle arguments = new Bundle();
        arguments.putInt(CHOICE, choice);
        fragment.setArguments(arguments);
        return fragment;
}

در ادامه کد کامل تری از ساخت فرگمنت در اکتیویتی می بینیم:

// Get the FragmentManager and start a transaction.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
                                          .beginTransaction();
// Instantiate the fragment.
SimpleFragment fragment =
                SimpleFragment.newInstance(mRadioButtonChoice);
// Add the fragment.
fragmentTransaction.add(R.id.fragment_container, fragment).commit();

قبل از اینکه ظاهر فرگمنت ترسیم شود، در متد ()onCreate یا ()onCreateView با استفاده از ()getArguments ، آرگومان های ارسالی را از باندل بخوانید و عملیات لازم را انجام دهید.

if (getArguments().containsKey(CHOICE)) {
    // A choice was made, so get the choice.
    mRadioButtonChoice = getArguments().getInt(CHOICE);
    // Check the radio button choice.
    if (mRadioButtonChoice != NONE) {
         radioGroup.check
             (radioGroup.getChildAt(mRadioButtonChoice).getId());
    }
}

ارسال داده از فرگمنت به اکتیویتی

برای ارسال داده از فرگمنت به اکتیویتی‌اش مراحل زیر را در فرگمنت طی کنید:

  • یک اینترفیس لیسنر با یک یا چند متد، برای ارتباط با اکتیویتی تعریف کنید.
  • تابع ()onAttach را override کنید تا در آن چک کنید اکتیویتی میزبان، اینترفیس را پیاده سازی کرده است یا خیر.
  • متدی که برای اینترفیس تعریف کردید را صدا کنید و داده‌هایی که میخواهید رو به عنوان پارامتر برایش ارسال کنید.

در اکتیویتی میزبان مراحل زیر را طی کنید:

  • اینترفیس تعریف شده در فرگمنت را پیاده‌سازی کنید.(همه اکتیویتی هایی که از این فرگمنت استفاده می‌کنند باید اینترفیس را پیاده سازی کنند)
  • بعد از پیاده سازی اینترفیس، از شما خواهد خواست تا متدهای اینترفیس را پیاده‌سازی کنید.لطفا متدها را برای گرفتن داده، پیاده‌سازی کنید.

در مثال زیر اینترفیس OnFragmentInteractionListener را در فرگمنت تعریف می‌کنیم. اینترفیس یک متد به نام ()onRadioButtonChoice دارد. اکتیویتی باید این اینترفیس را پیاده‌سازی کند. متد ()onAttach فرگمنت اگر اکتیویتی این اینترفیس را پیاده سازی کرده باشد یک رفرنس به آن تعریف می‌کند وگرنه یک Exception برمی‌گرداند.

// Interface definition and onFeedbackChoice() callback.
interface OnFragmentInteractionListener {
    void onRadioButtonChoice(int choice);
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new ClassCastException(context.toString()
                    + &quot must implement OnFragmentInteractionListener&quot);
    }
}

در ادامه می بینید که چطور متد ()onCheckedChanged درون فرگمنت وضعیت رادیو باتن‌ها را بررسی می‌کند و از طریق متد ()onRadioButtonChoice داده‌های دریافتی را برای اکتیویتی ارسال می‌کند.

public void onCheckedChanged(RadioGroup group, int checkedId) {
    View radioButton = radioGroup.findViewById(checkedId);
    int index = radioGroup.indexOfChild(radioButton);
    switch (index) {
        case YES: // User chose &quotYes.&quot
            textView.setText(R.string.yes_message);
            mRadioButtonChoice = YES;
            mListener.onRadioButtonChoice(YES);
            break;
        case NO: // User chose &quotNo.&quot
            textView.setText(R.string.no_message);
            mRadioButtonChoice = NO;
            mListener.onRadioButtonChoice(NO);
            break;
        default: // No choice made.
            mRadioButtonChoice = NONE;
            mListener.onRadioButtonChoice(NONE);
            break;
    }
}

برای استفاده از متدهای اینترفیس، برای بازیابی داده‌های ارسالی، اکتیویتی باید آن(ها) را پیاده‌سازی کرده باشد.

public class MainActivity extends AppCompatActivity
                implements SimpleFragment.OnFragmentInteractionListener {
   //...
}

و در نهایت می‌بینیم اکتیویتی از متد ()onRadioButtonChoice برای دریافت داده استفاده می‌کند.

@Override
public void onRadioButtonChoice(int choice) {
    mRadioButtonChoice = choice;
    Toast.makeText(this, &quotChoice is &quot + Integer.toString(choice),
                                            LENGTH_SHORT).show();
}

ممکن است برخی داده‌های فرگمنت در اکتیویتی میزبانش لازم شوند. اکتیویتی می‌تواند از متدهای اینترفیس فرگمنت برای بازیابی داده‌های مرتبط استفاده کند(همین روش گفته شده). اکتیویتی می‌تونه بعداً موقع بازسازی (recreating) فرگمنت داده‌ها رو برای فرگمنت ارسال کنه.
آنچه خواندید ترجمه‌ای از مرجع مفاهیمِ توسعه اندروید پیشرفته بود که یک دوره آموزشی است و توسط تیم آموزشی توسعه دهندگان گوگل ایجاد شده است. می‌توانید محتوای کامل دوره را ازینجا مطالعه بفرمایید.