onCreateView() вложенного фрагмента не вызывается

Я просматривал документацию по вложенным фрагментам, лучшие практики и все возможные ссылки здесь, в StackOverflow. Я видел предложения избегать использования тега фрагмента в файлах макета и вместо этого добавлять их через транзакцию для плавных переходов.

Прежде чем реализовать его в своем приложении, я попробовал простой пример и обнаружил неожиданное поведение, дочерние фрагменты onCreateView() не вызываются. Я предполагаю, что мне что-то не хватает в моем понимании, поэтому я ищу совет/помощь, чтобы разобраться с этим.

Пример потока показан ниже: --

MainActivity содержит фрагмент (MessageFragment), а MessageFragment содержит дочерний фрагмент (MessageTextFragment).

Я добавляю дочерний фрагмент программно, используя транзакцию фрагмента. Но onCreateView дочернего фрагмента никогда не вызывается, из-за чего подвид не раздувается.

Я везде использую библиотеку поддержки v4 для фрагментов. Вот мой полный код: --

Файл MainActivity: --

 public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.root_layout);
            FragmentManager fragmentManager = getSupportFragmentManager();
            Fragment fragment = null;
            if (savedInstanceState == null) {
                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                fragment=new MessageFragment().newInstance();
                fragmentTransaction.add(R.id.fragment_container, fragment);
                fragmentTransaction.commit();
            }
        }
}

файл макета для MainActivity: -

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/wizard_layout_root"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:orientation="vertical">
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"/>
</RelativeLayout>

Первый фрагмент: MessageFragment--

public class MessageFragment extends Fragment {
    private EditText msgText;

    private Activity activity;

    TextView tvTitle, tvContent, tvIntro;
    Button bAction;

    public static MessageFragment newInstance() {
        MessageFragment f = new MessageFragment();
        return (f);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.message, container, false);
        tvTitle = (TextView) view.findViewById(R.id.fragment_title);
        tvIntro = (TextView) view.findViewById(R.id.fragment_intro);
        tvContent = (TextView) view.findViewById(R.id.fragment_contents);
        bAction = (Button) view.findViewById(R.id.fragment_action);

        Fragment fragment = getChildFragmentManager().findFragmentById(R.id.sms_message);
        if (fragment == null) {
            Log.d("MESSAGE_FRAGMENT", "definetly inside");
            fragment = MessageEditFragment.newInstance();
            FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
            transaction.add(R.id.sms_message, fragment);
            transaction.commit();
            getChildFragmentManager().executePendingTransactions();
            Log.d("MESSAGE_FRAGMENT", "" + getChildFragmentManager().findFragmentById(R.id.sms_message));
        }
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        activity = getActivity();
        if (activity != null) {
            Fragment fragment = getChildFragmentManager().findFragmentById(R.id.sms_message);
            Log.d("MESSAGE_FRAGMENT", "onActivityCreated" + fragment);
            msgText = (EditText) ((MessageEditFragment)fragment).getView().findViewById(R.id.message_edit_text);
            bAction.setEnabled(!msgText.getText().toString().trim().equals(""));
            msgText.selectAll();
        }
    }
}

Макет MessageFragment (Framelayout — это контейнер для дочернего фрагмента)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:descendantFocusability="beforeDescendants"
    android:focusableInTouchMode="true"
    android:orientation="vertical">
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">

            <TextView
                android:id="@+id/fragment_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"/>

            <TextView
                android:id="@+id/fragment_intro"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@+id/fragment_title" />

            <LinearLayout
                android:id="@+id/ll_message"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/fragment_intro"
                android:layout_weight="1">
                <FrameLayout
                    android:id="@+id/sms_message"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent" />
            </LinearLayout>
            <Button
                android:id="@+id/fragment_action"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/ll_message"/>
        </RelativeLayout>
    </ScrollView>
</LinearLayout>

Это дочерний фрагмент

public class MessageEditFragment extends Fragment {

    private EditText messageEditText;

    private MessageLimitWatcher messageLimitWatcher;
    private int maxCharacters;
    private String messageHeader;
    private Button bAction;

    public static MessageEditFragment newInstance() {
        MessageEditFragment f = new MessageEditFragment();
        return(f);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d("MESSAGE_FRAGMENT", "onCreateView Of nested fragment");
        View view = inflater.inflate(R.layout.message_fragment, container, false);
        messageEditText = (EditText) view.findViewById(R.id.message_edit_text);
        messageEditText.requestFocus();
        return view;
    }

и его макет

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent">
    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="130dp">
        <EditText
            android:id="@+id/message_edit_text"
            android:layout_height="130dp"
            android:layout_width="fill_parent" />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"/>
    </RelativeLayout>
</LinearLayout>

Logcat никогда не печатает строку: --

Log.d("MESSAGE_FRAGMENT", "onCreateView вложенного фрагмента");

и я получаю исключение нулевого указателя, когда пытаюсь использовать текстовое поле дочернего фрагмента, так как оно не завышено. В основном, я получаю нулевой указатель по адресу: --

((MessageEditFragment)фрагмент).getView().findViewById(R.id.message_edit_text);


person Nicks    schedule 27.08.2015    source источник
comment
Во-первых, закомментируйте код, который выдает NPE. Добавьте операторы журнала в onCreateView(...) и onActivityCreated(...) для как дочерних, так и родительских фрагментов: Log.i("ParentFrag", "onCreateView(...) :::: Parent")... еще 3 оператора журнала. Проверьте последовательность выполнения. Я подозреваю, что дочерний onCreateView(...) называется после родительского onActivityCreated(...). Я могу предложить пару решений, если вы можете проверить это.   -  person Vikram    schedule 05.09.2015
comment
@Vikram, теперь это облегчение. Вы правы, я следовал тому, что вы сказали, и это был порядок: - вызываемый родитель onCreateView(), вызываемый родитель onActivityCreated, onCreateView вложенного фрагмента, а затем onActivityCreated вызываемого вложенного фрагмента. Итак, это означает, что я пытаюсь получить доступ к дочернему фрагменту раньше, чем он будет раздут. Можете ли вы предложить мне свои дальнейшие точки зрения, так как мне нужны поля дочерних фрагментов в родительских onActivityCreate.   -  person Nicks    schedule 06.09.2015
comment
@ Викрам, я очень благодарен тебе за то, что ты изучил это. В дополнение к этому я пошел другим путем. Поэтому я использовал поля parentFragment в onActivitycreated() для childFragment. используя getParentFragment(). Теперь мое приложение также нацелено на уровень API ‹16, где этот getFragmentManager() не поддерживается. Итак, вот где мне нужны ваши материалы. Является ли эта ссылка правильным способом доступа к родительским полям?   -  person Nicks    schedule 06.09.2015
comment
Кроме того, пожалуйста, добавьте свой ответ, я буду более чем счастлив принять его.   -  person Nicks    schedule 06.09.2015
comment
кажется, что getParentFragment() также доступен для библиотеки поддержки фрагментов. Но я действительно больше желаю услышать предлагаемые вами решения.   -  person Nicks    schedule 06.09.2015
comment
Как бы я решил это: объявить interface с void onTextChanged(String);. Пусть родительский фрагмент реализует этот interface и вносит свою лепту при вызове этого метода: bAction.setEnabled(!TextUtils.isEmpty(text) && !TextUtils.isEmpty(text.trim()));. Измените метод MessageEditFragment.newInstance(), чтобы получить экземпляр этого интерфейса. Когда фрагмент будет готов (onActivityCreated()), вызовите метод интерфейса и обновите состояние button. Это масштабируемое решение: если вам требуется «больше» связи между этими фрагментами, добавьте больше методов.   -  person Vikram    schedule 10.09.2015
comment
@Vikram, я понял вашу точку зрения. Если вы можете добавить свой ответ, я приму его, во всяком случае, благодаря вашей помощи я смог решить свою проблему. Спасибо еще раз.   -  person Nicks    schedule 18.09.2015


Ответы (2)


Не вызывайте транзакции внутри onCreateView либо используйте onActivityCteated, либо onCreate.

Также стоит отметить, что не вызывайте executePendingTransactions, когда вы находитесь внутри родительского фрагмента, это обязательно что-то сломает.

Попробуйте настроить родительский фрагмент примерно так:

public class MessageFragment extends Fragment {
  private EditText msgText;

  private Activity activity;

  TextView tvTitle, tvContent, tvIntro;
  Button bAction;

  public static MessageFragment newInstance() {
    MessageFragment f = new MessageFragment();
    return (f);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.message, container, false);
    tvTitle = (TextView) view.findViewById(R.id.fragment_title);
    tvIntro = (TextView) view.findViewById(R.id.fragment_intro);
    tvContent = (TextView) view.findViewById(R.id.fragment_contents);
    bAction = (Button) view.findViewById(R.id.fragment_action);
    return view;
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    activity = getActivity();
    if (activity != null) {
        Fragment fragment = getChildFragmentManager().findFragmentById(R.id.sms_message);
        Log.d("MESSAGE_FRAGMENT", "onActivityCreated" + fragment);
        msgText = (EditText) ((MessageEditFragment)fragment).getView().findViewById(R.id.message_edit_text);
        bAction.setEnabled(!msgText.getText().toString().trim().equals(""));
        msgText.selectAll();
    }
    Fragment fragment = MessageEditFragment.newInstance();
    getChildFragmentManager().beginTransaction()
      .replace(R.id.sms_message, fragment)
      .commit();
  }
}
person Chris.Jenkins    schedule 02.09.2015
comment
Позвольте мне попробовать это, я очень ценю ваше время - person Nicks; 02.09.2015
comment
Это буквально странно, я пробовал в onCreate, а также в onActivityCreated, но везде один и тот же нулевой указатель. Поле msgText определено в дочернем фрагменте, и я пытаюсь использовать его в родительском фрагменте, предполагая, что оно будет доступно после раздувания дочернего фрагмента. Но мне не повезло. oncreatVies дочернего фрагмента вообще не называется... - person Nicks; 02.09.2015
comment
Если вы можете просто попробовать, просто скопируйте и вставьте этот код, и если вы сможете найти то, что мне не хватает, это будет большим одолжением. - person Nicks; 02.09.2015

Возможно, вам не следует добавлять дочерний фрагмент (скажем, вызов transaction.add внутри фрагмента).

Выполняйте транзакцию фрагмента только в Activity.

Если вы хотите выполнить транзакцию фрагмента, вы можете уведомить Activity, в котором размещается текущий фрагмент, через EventBus или интерфейс, реализованный хост-активностью.

person Piasy    schedule 05.09.2015