web-dev-qa-db-fra.com

onActivityResult () non appelé dans la nouvelle API de fragment imbriqué

J'utilise la nouvelle fragment imbriqué API que Android inclut dans la bibliothèque de support.

Le problème auquel je suis confronté avec les fragments imbriqués est que, si un fragment imbriqué (c'est-à-dire un fragment qui a été ajouté à un autre fragment via FragmentManagerreturned by getChildFragmentManager()) appelle startActivityForResult(), la méthode onActivityResult() du fragment imbriqué n'est pas appelée. Cependant, la fonction onActivityResult() et la fonction onActivityResult() du fragment parent sont appelées.

Je ne sais pas si je manque quelque chose sur les fragments imbriqués, mais je ne m'attendais pas au comportement décrit. Voici le code qui reproduit ce problème. J'apprécierais beaucoup que quelqu'un puisse m'orienter dans la bonne direction et m'expliquer ce que je fais 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 est:

<?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>
75

Oui, la onActivityResult() dans le fragment imbriqué ne sera pas invoquée de cette façon.

La séquence d'appel de onActivityResult (dans Android) est

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

Dans la 3ème étape, le fragment se trouve dans le FragmentMananger du parent Activity. Donc, dans votre exemple, c'est le fragment de conteneur qui est trouvé pour distribuer onActivityResult(), le fragment imbriqué n'a jamais pu recevoir l'événement.

Je pense que vous devez implémenter votre propre dispatch dans ContainerFragment.onActivityResult(), trouver le fragment imbriqué et invoquer lui passer le résultat et les données.

57
faylon

J'ai résolu ce problème avec le code suivant (la bibliothèque de support est utilisée):

Dans le fragment de conteneur, remplacez onActivityResult de cette manière:

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

Désormais, le fragment imbriqué recevra un appel à la méthode onActivityResult.

En outre, comme notéEric Brynsvold dans une question similaire, le fragment imbriqué doit démarrer l'activité en utilisant son fragment parent et non le simple appel startActivityForResult (). Ainsi, dans un fragment imbriqué, commencez l'activité avec:

getParentFragment().startActivityForResult(intent, requestCode);
140
pvshnik

Voici comment je l'ai résolu.

En activité:

@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
whizzle

J'ai recherché la source FragmentActivity. Je trouve ces faits.

  1. lorsque le fragment appelle startActivityForResult (), il appelle en fait startAcitivityFromFragment () de son parent FragmentActivity.
  2. lorsque le fragment démarre l'activité pour le résultat, le requestCode doit être inférieur à 65536 (16 bits).
  3. dans startActivityFromFragment () de FragmentActivity, elle appelle Activity.startActivityForResult (), cette fonction a également besoin d'un requestCode, mais ce requestCode n'est pas égal au Origin requestCode.
  4. en fait, le requestCode dans FragmentActivity a deux parties. la valeur 16 bits la plus élevée est (l'index du fragment dans FragmentManager) + 1. la 16 bits inférieure est égale au code de demande d'origine. c'est pourquoi le requestCode doit être inférieur à 65536.

regardez le code dans 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));
}

lorsque le résultat est renvoyé.FragmentActivity remplace la fonction onActivityResult et vérifie si le 16 bits supérieur du requestCode n'est pas égal à 0, puis transmet onActivityResult à son fragment enfant.

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

c'est ainsi que FragmentActivity envoie l'événement onActivityResult.

lorsque vous avez une fragmentActivity, elle contient fragment1 et fragment1 contient fragment2. OnActivityResult ne peut donc passer qu'à fragment1.

Et que je trouve un moyen de résoudre ce problème. Créez d'abord un fragment NestedFragment extend Remplacez la fonction 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);
        }

    }
}

}

que de créer MyFragment étendre le remplacement de fragment sur la fonctionActivityResult.

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

}

C'est tout! Usage:

  1. fragment1 étend MyFragment.
  2. fragment2 étend NestedFragment.
  3. Pas plus différent. Appelez simplement startActivityForResult () sur fragment2 et remplacez la fonction onActivityResult ().

Waring !!!!!!!! Le requestCode doit être inférieur à 512 (8 bits), car nous avons renversé le requestCode en 3 parties

16bit | 8bit | 8 bits

le 16 bits supérieur Android déjà utilisé, le 8 bits du milieu est destiné à aider fragment1 à trouver le fragment2 dans son tableau fragmentManager. le 8 bits le plus bas est le Origin requestCode.

4
leikaiyi

Pour Androidx avec des composants de navigation utilisant NavHostFragment

Réponse mise à jour le 2 septembre 2019

Ce problème est de retour dans Androidx, lorsque vous utilisez une seule activité et que vous avez un fragment imbriqué dans la NavHostFragment, onActivityResult() n'est pas appelé pour le fragment enfant de NavHostFragment.

Pour résoudre ce problème, vous devez router manuellement l'appel vers la onActivityResult() des fragments enfants de onActivityResult() de l'activité hôte.

Voici comment le faire en utilisant le code Kotlin. Cela va à l'intérieur de la onActivityResult() de votre activité principale qui héberge la 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) }
    }

Cela garantira que la onActivityResult() des fragments enfants sera appelée normalement.

Pour Android Support Library

Ancienne réponse

Ce problème a été corrigé dans Android Support Library 23.2 en avant. Nous n'avons plus rien à faire de plus avec Fragment dans Android Support Library 23.2+. onActivityResult() est maintenant appelé dans le Fragment comme prévu.

Je viens de le tester en utilisant la bibliothèque de support Android 23.2.1 et cela fonctionne. Enfin, je peux avoir un code plus propre!

Donc, la solution consiste à commencer à utiliser la dernière Android Support Library.

4
Yogesh Umesh Vaity

Pour l'activité principale, vous écrivez OnActivityForResult avec Super class comme as

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

Pour l'activité Ecrire l'intention sans getActivity () comme as

 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 pour le fragment

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

}}

0
Binesh Kumar