onActivityResult () wird in der neuen API für verschachtelte Fragmente nicht aufgerufen

77

Ich habe die neue API für verschachtelte Fragmente verwendet , die Android in die Support-Bibliothek aufgenommen hat.

Das Problem, mit dem ich bei verschachtelten Fragmenten konfrontiert bin, besteht darin, dass die Methode des verschachtelten Fragments nicht aufgerufen wird, wenn ein verschachteltes Fragment (dh ein Fragment, das über die FragmentManagerRückgabe von zu einem anderen Fragment hinzugefügt wurde getChildFragmentManager()) aufgerufen wird. Es werden jedoch sowohl das übergeordnete Fragment als auch die Aktivität aufgerufen.startActivityForResult()onActivityResult()onActivityResult()onActivityResult()

Ich weiß nicht, ob mir etwas an verschachtelten Fragmenten fehlt, aber ich habe das beschriebene Verhalten nicht erwartet. Unten finden Sie den Code, der dieses Problem reproduziert. Ich würde mich sehr freuen, wenn mich jemand in die richtige Richtung weisen und mir erklären kann, was ich falsch mache:

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 ist:

<?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>
Knöchel die Echidna
quelle
Habe vor einer Woche den gleichen Fehler gefunden. Musste onActivityResult () für das verschachtelte Fragment manuell aufrufen. Persönlich denke ich, dass es ein Fehler ist. Ich habe noch nicht überprüft, ob es mit dem Framework machbar ist, aber ich denke, es könnte sein. Sie müssen nur alle untergeordneten Fragmente auf das Fragment der obersten Ebene überprüfen und für jedes von ihnen onActivityResult () aufrufen. Wenn es keine Möglichkeit gibt, dies innerhalb des Frameworks zu tun, dann ist das in Ordnung. Aber wenn ja, halte ich das ernsthaft für einen Fehler.
Bogdan Zurac

Antworten:

58

Ja, das onActivityResult()verschachtelte Fragment wird auf diese Weise nicht aufgerufen.

Die Aufrufsequenz von onActivityResult (in der Android-Supportbibliothek) lautet

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

Im dritten Schritt befindet sich das Fragment im FragmentManangerübergeordneten Element Activity. In Ihrem Beispiel wird also das Containerfragment zum Versenden gefunden onActivityResult(). Das verschachtelte Fragment könnte das Ereignis niemals empfangen.

Ich denke, Sie müssen Ihren eigenen Versand implementieren ContainerFragment.onActivityResult(), das verschachtelte Fragment finden und das Ergebnis und die Daten an dieses übergeben.

Faylon
quelle
2
Ok, die Frage ist also ... ist das ein beabsichtigtes Verhalten oder ist es eine Art Fehler? Für mich ist es zumindest kontraintuitiv, dass das verschachtelte Fragment nicht das Ergebnis der Aktivität erhält.
Knuckles the Echidna
Ok .. Sie können es als Fehler betrachten, da weder die Android-Unterstützungsbibliothek noch der native Code von Version 4.2 das Ergebnis an ein verschachteltes Fragment senden konnten. Versuchen Sie, dies selbst zu lösen, nicht sehr schwer ...
Faylon
Nun, ich denke, das werde ich tun. Ich habe versucht, dies zu vermeiden, um keine weitere Kopplung zwischen der Hosting-Aktivität und ihren Fragmenten einzuführen.
Knuckles the Echidna
6
Es wurde ein Fehler dagegen gemeldet
Markus Junginger
Scheint, dass dieser Fehler noch nicht behoben wurde ...?
RRTW
145

Ich habe dieses Problem mit dem folgenden Code gelöst (Unterstützungsbibliothek wird verwendet):

Überschreiben Sie onActivityResult im Containerfragment folgendermaßen:

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

Jetzt erhält das verschachtelte Fragment einen Aufruf der onActivityResult-Methode.

Wie Eric Brynsvold in einer ähnlichen Frage bemerkte , sollte das verschachtelte Fragment die Aktivität mit dem übergeordneten Fragment und nicht mit dem einfachen Aufruf startActivityForResult () starten. Beginnen Sie also im verschachtelten Fragment die Aktivität mit:

getParentFragment().startActivityForResult(intent, requestCode);
pvshnik
quelle
2
Zu Ihrer Information Ich verwende pagertabstrip mit viewpager in firstfragment und lade viele untergeordnete Fragmente in viewpager
LOG_TAG
1
Laut Stacktrace tritt die NullPoinerException irgendwo in Ihrer FirstFragment.onActivityResult () -Methode auf. Ich denke, Sie können in dieser Methode einen Haltepunkt setzen und herausfinden, welche Zeile diese Ausnahme erzeugt und warum dies geschieht.
pvshnik
3
Dies ist eine großartige Antwort. Es ist nicht nur einfach und unkompliziert, es löst auch das gesamte untere 16-Bit-Problem mit verschachtelten Fragmenten und Aktivitätsergebnissen. Nett!
Mittelmania
1
Diese letzte Zeile sollte die erste sein :-)
Martin Nuc
1
Dies sollte die akzeptierte Antwort sein. Eine etwas [vielleicht übermäßig paranoid] robustere Antwort finden Sie unter gist.github.com/artem-zinnatullin/6916740 .
Swooby
8

So habe ich es gelöst.

In Aktivität:

@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);
        }
    }
}
zischen
quelle
Danke für deinen Trick! Aber können Sie erklären, was die IHandleActivityResult- Schnittstelle im Code ist, indem Sie Ihre Antwort bearbeiten? +1
LOG_TAG
1
IHandleActivityResult ist nur eine leere Schnittstelle. Fragmente, an die das handleResult übergeben werden soll, können die leere Schnittstelle implementieren. Es ist nicht nötig, aber ich fand es nützlich, damit nicht alle Fragmente den Rückruf erhielten.
Whizzle
5

Für Androidx mit Navigationskomponenten unter Verwendung von NavHostFragment

Aktualisierte Antwort am 2. September 2019

Dieses Problem tritt wieder in Androidx auf, wenn Sie eine einzelne Aktivität verwenden und ein verschachteltes Fragment in der haben NavHostFragment, onActivityResult()wird das untergeordnete Fragment von nicht aufgerufen NavHostFragment.

Um dies zu beheben, müssen Sie den Aufruf manuell an die onActivityResult()untergeordneten Fragmente onActivityResult()der Hostaktivität weiterleiten.

Hier erfahren Sie, wie Sie dies mit Kotlin-Code tun. Dies gehört onActivityResult()zu Ihrer Hauptaktivität, in der Folgendes ausgeführt wird 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) }
}

Dadurch wird sichergestellt, dass die onActivityResult()untergeordneten Fragmente normal aufgerufen werden.

Für Android Support Library

Alte Antwort

Dieses Problem wurde ab Android Support Library 23.2 behoben . FragmentIn der Android Support Library 23.2+ müssen wir nichts mehr tun. onActivityResult()wird nun Fragmentwie erwartet im verschachtelten aufgerufen .

Ich habe es gerade mit der Android Support Library 23.2.1 getestet und es funktioniert. Endlich kann ich saubereren Code haben!

Die Lösung besteht also darin, die neueste Android-Support-Bibliothek zu verwenden.

Yogesh Umesh Vaity
quelle
Kannst du bitte einen offiziellen Link dazu teilen?
Ravi
1
@ RaviRupareliya Ich habe die Antwort mit dem Link aktualisiert.
Yogesh Umesh Vaity
4

Ich habe die FragmentActivity-Quelle durchsucht. Ich finde diese Fakten.

  1. Wenn ein Fragment startActivityForResult () aufruft, ruft es tatsächlich startAcitivityFromFragment () seines übergeordneten FragmentActivity auf.
  2. Wenn das Fragment die Aktivität für das Ergebnis startet, muss der requestCode unter 65536 (16 Bit) liegen.
  3. In startActivityFromFragment () von FragmentActivity wird Activity.startActivityForResult () aufgerufen. Diese Funktion benötigt ebenfalls einen requestCode, aber dieser requestCode entspricht nicht dem ursprünglichen requestCode.
  4. Tatsächlich besteht der requestCode in FragmentActivity aus zwei Teilen. Der höhere 16-Bit-Wert ist (der Fragmentindex in FragmentManager) + 1. Der niedrigere 16-Bit-Wert entspricht dem Ursprungsanforderungscode. Deshalb muss der requestCode unter 65536 liegen.

Beobachten Sie den Code in 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));
}

Wenn das Ergebnis zurück.FragmentActivity die Funktion onActivityResult überschreibt und prüft, ob das höhere 16-Bit des requestCode nicht 0 ist, übergeben Sie das onActivityResult an das untergeordnete Fragment.

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

So löst FragmentActivity das Ereignis onActivityResult aus.

Wenn Sie eine fragmentActivity haben, enthält sie fragment1 und fragment1 enthält fragment2. OnActivityResult kann also nur an fragment1 übergeben werden.

Und dann finde ich einen Weg, um dieses Problem zu lösen. Erstellen Sie zuerst ein NestedFragment-Erweiterungsfragment, das die Funktion startActivityForResult überschreibt.

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

    }
}

}}

als MyFragment erstellen erweitern Fragment überschreiben onActivityResult Funktion.

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

}}

Das ist alles! Verwendung:

  1. fragment1 erweitert MyFragment.
  2. fragment2 erweitert NestedFragment.
  3. Nicht mehr anders. Rufen Sie einfach startActivityForResult () für Fragment2 auf und überschreiben Sie die Funktion onActivityResult ().

Warnung !!!!!!!! Der requestCode muss unter 512 (8 Bit) liegen, da wir den requestCode in drei Teilen verschüttet haben

16bit | 8bit | 8 Bit

Das höhere 16-Bit-Android, das bereits verwendet wird, und das mittlere 8-Bit-Format sollen Fragment1 dabei helfen, das Fragment2 in seinem fragmentManager-Array zu finden. Das niedrigste 8-Bit-Format ist der Ursprungsanforderungscode.

Leikaiyi
quelle
0

Für die Hauptaktivität schreiben Sie OnActivityForResult mit der Super-Klasse wie

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

Für Aktivität Schreibe Absicht ohne getActivity () wie als

 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 für Fragment

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

}}

Binesh Kumar
quelle
-2

Ich hatte das gleiche Problem! Ich löste es durch statische Verwendung ChildFragmentin der ParentFragment, wie folgt aus :

private static ChildFragment mChildFragment;

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

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

}
JulianLiu
quelle
1
mChildFragment ist unnötig statisch.
foo64