onActivityResult () не викликається в новому API вкладених фрагментів


77

Я використовую новий вкладений фрагмент 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>

Знайшов ту саму помилку тиждень тому. Довелося вручну викликати onActivityResult () у вкладеному фрагменті. Особисто я думаю, що це помилка. Насправді не перевіряв, чи це можливо з фреймворку, але я думаю, що це може бути. Їм просто потрібно перевірити всі дочірні фрагменти на наявність фрагменту верхнього рівня і викликати onActivityResult () на кожному з них. Якщо немає способу зробити це в рамках, то гаразд, це все. Але якщо це так, я серйозно вважаю цю помилку.
Богдан Зурак

Відповіді:


58

Так, onActivityResult()вкладений фрагмент таким чином не буде викликаний.

Послідовність виклику onActivityResult (у бібліотеці підтримки Android):

  1. Activity.dispatchActivityResult().
  2. FragmentActivity.onActivityResult().
  3. Fragment.onActivityResult().

На третьому кроці фрагмент знаходиться в FragmentManangerбатьківському Activity. Отже, у вашому прикладі виявляється, що саме фрагмент контейнера відправляється onActivityResult(), вкладений фрагмент ніколи не може отримати подію.

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


2
Гаразд, отже, питання ... це задумана поведінка, чи це якась помилка? Для мене це принаймні протилежне інтуїтивне, що вкладений фрагмент не отримує результату діяльності.
Knuckles the Echidna

Гаразд .. Ви можете розглядати це як помилку, оскільки ні бібліотека підтримки Android, ні власний код версії 4.2 не можуть надіслати результат до вкладеного фрагмента. Спробуйте вирішити це самостійно, не дуже важко ...
Файлон,

Ну, мабуть, це я і зроблю. Я намагався цього уникнути, щоб не створювати більше взаємозв'язку між хостинговою діяльністю та її фрагментами.
Knuckles the Echidna

6
Помилка подана проти нього: code.google.com/p/android/issues/detail?id=40537
Маркус Юнгінгер,

Здається, ця помилка просто ще не виправлена ​​...?
RRTW

145

Я вирішив цю проблему за допомогою наступного коду (використовується бібліотека підтримки):

У фрагменті контейнера перевизначення 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);

2
FYI Я використовую стрічкову стрічку з переглядачем у першому фрагменті та завантажуючи багато дочірніх фрагментів у переглядач
LOG_TAG

1
Stacktrace каже, що NullPoinerException відбувається десь у вашому методі FirstFragment.onActivityResult (). Я думаю, ви можете вказати точку зупинки в цьому методі і зрозуміти, який рядок дає цей виняток і чому це відбувається.
pvshnik

3
Це чудова відповідь. Мало того, що це просто і просто, це вирішує всю нижчу 16-бітову проблему з вкладеними фрагментами та результатами діяльності. Приємно!
mittelmania

1
Цей останній рядок повинен бути першим :-)
Мартін Нук,

1
Це має бути прийнятою відповіддю. Трохи більш [можливо надмірно параноїчно] надійну відповідь можна знайти за адресою gist.github.com/artem-zinnatullin/6916740 .
swooby

8

Ось як я це вирішив.

В діяльності:

@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);
        }
    }
}

Дякую за твій фокус! але чи можете ви пояснити, що таке інтерфейс IHandleActivityResult у коді, відредагувавши свою відповідь? +1
LOG_TAG

1
IHandleActivityResult - це просто порожній інтерфейс. Фрагменти, які хочуть передавати йому handleResult, можуть реалізовувати порожній інтерфейс. Це непотрібно, але я знайшов це корисним, так що не всі фрагменти отримували зворотний дзвінок.
свист

6

Для 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.


Ви можете поділитися будь-яким офіційним посиланням для цього?
Раві

1
@RaviRupareliya Я оновив відповідь за посиланням.
Yogesh Umesh Vaity

4

Я шукаю джерело FragmentActivity, я знаходжу ці факти.

  1. коли фрагмент викликає startActivityForResult (), він фактично викликає його батьківський FragmentActivity's startAcinityFromFragment ().
  2. коли фрагмент починає активність результату, requestCode повинен бути нижче 65536 (16 біт).
  3. у FragmentActivity's startActivityFromFragment (), він викликає Activity.startActivityForResult (), цій функції також потрібен requestCode, але цей requestCode не дорівнює початковому requestCode.
  4. насправді requestCode у FragmentActivity має дві частини. вище 16-бітне значення (індекс фрагмента у FragmentManager) + 1. нижче 16-бітне дорівнює коду запиту про вихід. тому 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));
}

при поверненні результату.

    @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, він містить fragment1, а fragment1 містить fragment2. Тож 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 розширити функцію перевизначення фрагмента 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. fragment1 розширити MyFragment.
  2. fragment2 розширити NestedFragment.
  3. Більше не інакше. Просто викликайте startActivityForResult () на фрагменті 2 і перевизначте функцію onActivityResult ().

З обережністю !!!!!!!! Код requestCode повинен містити менше 512 (8 біт), тому що ми розлили requestCode в 3 частині

16 біт | 8 біт | 8 біт

чим старший 16-бітний Android вже використовується, середній 8-бітний повинен допомогти fragment1 знайти фрагмент2 у його масиві fragmentManager. найнижчий 8-бітний - код запиту на вихід.


0

Для Основної діяльності ви пишете 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) {

}}


-2

Я зіткнувся з тією ж проблемою! Я вирішив це, використовуючи статику ChildFragmentв ParentFragment, наприклад:

private static ChildFragment mChildFragment;

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

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

}

1
mChildFragment непотрібно статичний.
foo64 02.03.15
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.