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

Я использую новый вложенный фрагмент API, который Android включает в библиотеку поддержки.

Проблема, с которой я сталкиваюсь с вложенными фрагментами, заключается в том, что если вложенный фрагмент (т.е. фрагмент, добавленный в другой фрагмент через FragmentManager, возвращаемый getChildFragmentManager()), вызывает startActivityForResult(), вложенный фрагмент onActivityResult() не вызывается. Однако как родительский фрагмент onActivityResult(), так и активность onActivityResult() действительно вызываются.

Я не знаю, не хватает ли я чего-то о вложенных фрагментах, но я не ожидал описанного поведения. Ниже приведен код, воспроизводящий эту проблему. Я был бы очень признателен, если кто-то может указать мне в правильном направлении и объяснить мне, что я делаю неправильно:

package com.example.nestedfragmentactivityresult;

import android.media.RingtoneManager;
import android.os.Bundle;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends FragmentActivity
{
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        this.getSupportFragmentManager()
            .beginTransaction()
            .add(android.R.id.content, new ContainerFragment())
            .commit();
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);

        // This is called
        Toast.makeText(getApplication(),
            "Consumed by activity",
            Toast.LENGTH_SHORT).show();
    }

    public static class ContainerFragment extends Fragment
    {
        public final View onCreateView(LayoutInflater inflater,
                                       ViewGroup container,
                                       Bundle savedInstanceState)
        {
            View result = inflater.inflate(R.layout.test_nested_fragment_container,
                container,
                false);

            return result;
        }

        public void onActivityCreated(Bundle savedInstanceState)
        {
            super.onActivityCreated(savedInstanceState);
            getChildFragmentManager().beginTransaction()
                .add(R.id.content, new NestedFragment())
                .commit();
        }

        public void onActivityResult(int requestCode,
                                     int resultCode,
                                     Intent data)
        {
            super.onActivityResult(requestCode, resultCode, data);

            // This is called
            Toast.makeText(getActivity(),
                "Consumed by parent fragment",
                Toast.LENGTH_SHORT).show();
        }
    }

    public static class NestedFragment extends Fragment
    {
        public final View onCreateView(LayoutInflater inflater,
                                       ViewGroup container,
                                       Bundle savedInstanceState)
        {
            Button button = new Button(getActivity());
            button.setText("Click me!");
            button.setOnClickListener(new View.OnClickListener()
            {
                public void onClick(View v)
                {
                    Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
                    startActivityForResult(intent, 0);
                }
            });

            return button;
        }

        public void onActivityResult(int requestCode,
                                     int resultCode,
                                     Intent data)
        {
            super.onActivityResult(requestCode, resultCode, data);

            // This is NOT called
            Toast.makeText(getActivity(),
                "Consumed by nested fragment",
                Toast.LENGTH_SHORT).show();
        }
    }
}

test_nested_fragment_container.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

</FrameLayout>

Ответ 1

Да, onActivityResult() во вложенном фрагменте не будет вызван таким образом.

Вызывающая последовательность onActivityResult (в библиотеке поддержки Android) -

  • Activity.dispatchActivityResult().
  • FragmentActivity.onActivityResult().
  • Fragment.onActivityResult().

На третьем этапе фрагмент находится в FragmentMananger родительского Activity. Таким образом, в вашем примере обнаружен фрагмент контейнера для отправки onActivityResult(), вложенный фрагмент никогда не сможет получить событие.

Я думаю, что вам нужно реализовать свою собственную отправку в ContainerFragment.onActivityResult(), найти вложенный фрагмент и вызвать передать ему результат и данные.

Ответ 2

Я решил эту проблему со следующим кодом (используется библиотека поддержки):

В контейнерном фрагменте переопределяют onActivityResult таким образом:

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        List<Fragment> fragments = getChildFragmentManager().getFragments();
        if (fragments != null) {
            for (Fragment fragment : fragments) {
                fragment.onActivityResult(requestCode, resultCode, data);
            }
        }
    }

Теперь вложенный фрагмент получит вызов метода onActivityResult.

Кроме того, как отметил Эрик Бринсволд в аналогичном вопросе, вложенный фрагмент должен начать работу, используя его родительский фрагмент а не простой вызов startActivityForResult(). Таким образом, во вложенной операции начала фрагмента с помощью:

getParentFragment().startActivityForResult(intent, requestCode);

Ответ 3

Вот как я это решил.

В действии:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    if (frags != null) {
        for (Fragment f : frags) {
            if (f != null)
                handleResult(f, requestCode, resultCode, data);
        }
    }
}

private void handleResult(Fragment frag, int requestCode, int resultCode, Intent data) {
    if (frag instanceof IHandleActivityResult) { // custom interface with no signitures
        frag.onActivityResult(requestCode, resultCode, data);
    }
    List<Fragment> frags = frag.getChildFragmentManager().getFragments();
    if (frags != null) {
        for (Fragment f : frags) {
            if (f != null)
                handleResult(f, requestCode, resultCode, data);
        }
    }
}

Ответ 4

Для Androidx с компонентами навигации, использующими NavHostFragment

Обновленный ответ от 2 сентября 2019 года

Эта проблема вернулась в Androidx, когда вы используете одно действие и у вас есть вложенный фрагмент внутри NavHostFragment, onActivityResult() не вызывается для дочернего фрагмента NavHostFragment.

Чтобы это исправить, вам нужно вручную перенаправить вызов к onActivityResult() дочерних фрагментов из onActivityResult() основного действия.

Вот как это сделать, используя код Kotlin. Это входит в onActivityResult() вашей основной деятельности, в которой находится NavHostFragment:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.your_nav_host_fragment)
    val childFragments = navHostFragment?.childFragmentManager?.fragments
    childFragments?.forEach { it.onActivityResult(requestCode, resultCode, data) }
}

Это обеспечит нормальный вызов onActivityResult() дочерних фрагментов.

Для библиотеки поддержки Android

Старый ответ

Эта проблема была исправлена в библиотеке поддержки Android 23.2 и далее. Нам больше не нужно ничего делать с Fragment в библиотеке поддержки Android 23. 2+. onActivityResult() теперь вызывается во вложенном Fragment, как и ожидалось.

Я только что протестировал его с помощью библиотеки поддержки Android 23.2.1, и он работает. Наконец-то я могу получить более чистый код!

Итак, решение состоит в том, чтобы начать использовать последнюю версию библиотеки поддержки Android.

Ответ 5

У меня есть поиск источника FragmentActivity. Я нахожу эти факты.

  • Когда вызов фрагмента startActivityForResult(), он фактически вызывает его родительский FragmentActivity startAcitivityFromFragment().
  • когда операция начала фрагментации для результата, код запроса должен быть ниже 65536 (16 бит).
  • в FragmentActivity startActivityFromFragment(), он вызывает Activity.startActivityForResult(), эта функция также нуждается в requestCode, но этот код запроса не равен исходному запросуCode.
  • на самом деле requestCode в FragmentActivity имеет две части. более высокое значение 16 бит (индекс фрагмента в FragmentManager) + 1. нижний 16 бит равен коду запроса origin. что почему requestCode должен быть ниже 65536.

смотрите код в FragmentActivity.

public void startActivityFromFragment(Fragment fragment, Intent intent,
        int requestCode) {
    if (requestCode == -1) {
        super.startActivityForResult(intent, -1);
        return;
    }
    if ((requestCode&0xffff0000) != 0) {
        throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
    }
    super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
}

когда результат возвращается. FragmentActivity переопределяет функцию onActivityResult и проверяет, является ли requestCode более высоким 16 бит не 0, чем передает onActivityResult его фрагменту.

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    mFragments.noteStateNotSaved();
    int index = requestCode>>16;
    if (index != 0) {
        index--;
        final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
        if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
            Log.w(TAG, "Activity result fragment index out of range: 0x"
                    + Integer.toHexString(requestCode));
            return;
        }
        final List<Fragment> activeFragments =
                mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
        Fragment frag = activeFragments.get(index);
        if (frag == null) {
            Log.w(TAG, "Activity result no fragment exists for index: 0x"
                    + Integer.toHexString(requestCode));
        } else {
            frag.onActivityResult(requestCode&0xffff, resultCode, data);
        }
        return;
    }

    super.onActivityResult(requestCode, resultCode, data);
}

чтобы как FragmentActivity отправил событие onActivityResult.

когда у вас есть функция fragmentActivity, она содержит фрагмент1, а фрагмент1 содержит фрагмент2. Так что onActivityResult может перейти только к фрагменту1.

И чем я нахожу способ решить эту проблему. Сначала создайте фрагмент расширенного фрагмента NestedFragment, переопределите функцию startActivityForResult.

public abstract class NestedFragment extends Fragment {

@Override
public void startActivityForResult(Intent intent, int requestCode) {

    List<Fragment> fragments = getFragmentManager().getFragments();
    int index = fragments.indexOf(this);
    if (index >= 0) {
        if (getParentFragment() != null) {
            requestCode = ((index + 1) << 8) + requestCode;
            getParentFragment().startActivityForResult(intent, requestCode);
        } else {
            super.startActivityForResult(intent, requestCode);
        }

    }
}

}

чем создать расширение MyFragment Extrag для функции onActivityResult.

public abstract class MyFragment extends Fragment {

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    int index = requestCode >> 8;
    if (index > 0) {
        //from nested fragment
        index--;

        List<Fragment> fragments = getChildFragmentManager().getFragments();
        if (fragments != null && fragments.size() > index) {
            fragments.get(index).onActivityResult(requestCode & 0x00ff, resultCode, data);
        }
    }
}

}

Это все! Использование:

  • фрагмент1 расширяет MyFragment.
  • фрагмент2 расширяет NestedFragment.
  • Больше не отличается. Просто вызовите startActivityForResult() на фрагменте2 и переопределите функцию onActivityResult().

Уоринг!!!!!!!! Код Request должен указывать 512 (8 бит), потому что мы пролили requestCode на 3 части

16 бит | 8bit | 8bit

более высокий 16-битный Android уже используется, средний 8 бит - помочь фрагменту1 найти фрагмент2 в его массиве arrayManager. Самый низкий 8 бит - это код запроса origin.

Ответ 6

Для основной деятельности вы пишете OnActivityForResult с классом Super, например

  @Override
public void onActivityResult(int requestCode, int resultCode, Intent result) {
    super.onActivityResult(requestCode, resultCode, result);
    if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
        beginCrop(result.getData());
    } }

Задание Начать запись без getActivity() как

 Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
        pickContactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
       startActivityForResult(pickContactIntent, 103);

OnActivityResult для фрагмента

   @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 101 && resultCode == getActivity().RESULT_OK && null != data) {

}}

Ответ 7

Я столкнулся с той же проблемой! Я решил это, используя static ChildFragment в ParentFragment, например:

private static ChildFragment mChildFragment;

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    mChildFragment.onActivityResult(int requestCode, int resultCode, Intent data);

}