No se llama a onActivityResult() en la nueva API de fragmentos anidados


He estado usando la nueva API fragmento anidado que Android incluye en la biblioteca de soporte.

El problema que estoy enfrentando con los fragmentos anidados es que, si un fragmento anidado (es decir, un fragmento que se ha agregado a otro fragmento a través del FragmentManagerdevuelto por getChildFragmentManager()) llama a startActivityForResult(), el método onActivityResult() del fragmento anidado no se llama. Sin embargo, se llama tanto al fragmento padre onActivityResult() como a la actividad onActivityResult().

No se si me estoy perdiendo algo sobre fragmentos anidados, pero no esperaba el comportamiento descrito. A continuación se muestra el código que reproduce este problema. Apreciaría mucho si alguien me puede señalar en la dirección correcta y explicarme lo que estoy haciendo mal:

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

<?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>
Author: Fllo, 2012-11-27

7 answers

Sí, el onActivityResult() en el fragmento anidado no se invocará de esta manera.

La secuencia de llamada de onActivityResult (en la biblioteca de soporte de Android) es

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

En el 3er paso, el fragmento se encuentra en el FragmentMananger del padre Activity. Así que en su ejemplo, es el fragmento contenedor que se encuentra para enviar onActivityResult(), el fragmento anidado nunca podría recibir el evento.

Creo que tienes que implementar tu propio dispatch en ContainerFragment.onActivityResult(), encuentra el fragmento anidado e invoke pasa el resultado y los datos a él.

 57
Author: faylon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-25 01:41:18

Resolví este problema con el siguiente código (se usa la biblioteca de soporte):

En el fragmento de contenedor anular Onactivityresultar de esta manera:

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

Ahora el fragmento anidado recibirá una llamada al método onActivityResult.

También, como señaló Eric Brynsvold en una pregunta similar, el fragmento anidado debería iniciar la actividad usando su fragmento padre y no la simple llamada startActivityForResult (). Por lo tanto, en el fragmento anidado iniciar actividad con:

getParentFragment().startActivityForResult(intent, requestCode);
 138
Author: pvshnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:53

Así es como lo resolví.

En Actividad:

@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);
        }
    }
}
 8
Author: whizzle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-16 00:32:52

Este problema se ha solucionado en Android Support Library 23.2 en adelante. Ya no tenemos que hacer nada extra con Fragment en la Biblioteca de soporte de Android 23.2+. onActivityResult() ahora se llama en el Fragment anidado como se esperaba.

Acabo de probarlo usando la Biblioteca de soporte de Android 23.2.1 y funciona. ¡Finalmente puedo tener un código más limpio!

Por lo tanto, la solución es comenzar a usar la última biblioteca de soporte de Android.

 4
Author: Yogesh Umesh Vaity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-05 13:43:12

Tengo que buscar en la fuente FragmentActivity.Encuentro estos hechos.

  1. cuando fragment llama a startActivityForResult(), en realidad llama a su padre FragmentActivity startAcitivityFromFragment().
  2. cuando el fragmento inicia la actividad para el resultado,el código de solicitud debe ser inferior a 65536(16 bits).
  3. en la función startActivityFromFragment () de FragmentActivity,llama a Activity.startActivityForResult (), esta función también necesita un requestCode, pero este requestCode no es igual al origin requestCode.
  4. en realidad el código de solicitud en FragmentActivity tiene dos partes. el valor de 16 bits más alto es (el índice del fragmento en FragmentManager) + 1.el 16bit inferior es igual al código de solicitud de origen. es por eso que el código de solicitud debe ser inferior a 65536.

Mira el código en 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));
}

Cuando el resultado vuelva.FragmentActivity sobrescribe la función onActivityResult, y comprueba si el 16bit superior del requestCode no es 0, que pasa el onActivityResult a sus hijos fragmento.

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

Así es como FragmentActivity envía el evento onActivityResult.

Cuando tienes una FragmentActivity, contiene fragment1, y fragment1 contiene fragment2. Así que onActivityResult solo puede pasar a fragment1.

Y entonces encuentro una manera de resolver este problema. Primero crea un fragmento de extensión NestedFragment sobrescribe la función 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);
        }

    }
}

}

Entonces crear mifragmento extender Anulación de fragmento onActivityResult función.

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

}

Eso es todo! Uso:

  1. fragment1 extender MiFragment.
  2. fragment2 extiende NestedFragment.
  3. No más diferente.Simplemente llame a startActivityForResult() en fragment2, y anule la función onActivityResult ().

Waring!!!!!!!! El requestCode debe estar debajo de 512 (8bit),porque derramamos el requestCode en 3 part

16bit | 8bit / 8bit

El Android de 16 bits más alto ya utilizado, el 8bit medio es ayuda a fragment1 a encontrar el fragment2 en su matriz FragmentManager.el 8bit más bajo es el código de solicitud de origen.

 3
Author: leikaiyi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-06 09:50:42

Para la Actividad Principal se escribe OnActivityForResult con Super clase como como

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

Para la intent de escritura de actividad sin getActivity() como

 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 for fragment

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

}}

 0
Author: Binesh Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-02 10:06:07

Me enfrenté al mismo problema! Lo resolví usando estático ChildFragment en el ParentFragment, así:

private static ChildFragment mChildFragment;

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

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

}
 -2
Author: JulianLiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-09 02:26:55