Nice programing

새 중첩 조각 API에서 onActivityResult ()가 호출되지 않음

nicepro 2020. 10. 15. 21:40
반응형

새 중첩 조각 API에서 onActivityResult ()가 호출되지 않음


Android가 지원 라이브러리에 포함 하는 새로운 중첩 조각 API를 사용하고 있습니다.

중첩 된 조각에 직면 한 문제는 중첩 된 조각 (즉, 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의 호출 순서 (Android 지원 라이브러리에 있음)는 다음과 같습니다.

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

세 번째 단계에서 조각은 FragmentManangerof parent 에서 발견됩니다 Activity. 따라서 귀하의 예에서를 전달하는 것은 컨테이너 조각 onActivityResult()이며 중첩 조각은 이벤트를 수신 할 수 없습니다.

에서 자신의 디스패치를 ​​구현 ContainerFragment.onActivityResult()하고 중첩 된 조각을 찾아서 결과와 데이터를 전달해야한다고 생각합니다.


다음 코드로이 문제를 해결했습니다 (지원 라이브러리가 사용됨).

컨테이너 조각에서 다음과 같이 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 메서드에 대한 호출을받습니다.

또한 유사한 질문에서 Eric Brynsvold언급했듯이 중첩 조각은 간단한 startActivityForResult () 호출이 아닌 부모 조각을 사용하여 활동을 시작해야합니다. 따라서 중첩 조각에서 다음을 사용하여 활동을 시작하십시오.

getParentFragment().startActivityForResult(intent, requestCode);

해결 방법은 다음과 같습니다.

활동 중 :

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

FragmentActivity 소스를 검색했는데 이러한 사실을 찾습니다.

  1. 조각이 startActivityForResult ()를 호출 할 때 실제로 부모 FragmentActivity의 startAcitivityFromFragment ()를 호출합니다.
  2. 결과에 대한 조각 시작 활동이있을 때 requestCode는 65536 (16bit) 미만이어야합니다.
  3. FragmentActivity의 startActivityFromFragment ()에서 Activity.startActivityForResult ()를 호출하고이 함수에도 requestCode가 필요하지만이 requestCode는 원본 requestCode와 동일하지 않습니다.
  4. 실제로 FragmentActivity의 requestCode에는 두 부분이 있습니다. 더 높은 16 비트 값은 (FragmentManager의 조각 인덱스) + 1. 더 낮은 16 비트는 원본 requestCode와 동일합니다. 이것이 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 함수를 재정의하고 onActivityResult를 자식 조각에 전달하는 것보다 requestCode의 높은 16 비트가 0이 아닌지 확인합니다.

    @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는 fragment1에만 전달할 수 있습니다.

그리고이 문제를 해결할 방법을 찾는 것보다. 먼저 NestedFragment 확장 Fragment를 만들고 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);
        }

    }
}

}

than create MyFragment extend Fragment override onActivityResult function.

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

}

That's all! Usage:

  1. fragment1 extend MyFragment.
  2. fragment2 extend NestedFragment.
  3. No more different.Just call startActivityForResult() on fragment2, and override the onActivityResult() function.

Waring!!!!!!!! The requestCode must bellow 512(8bit),because we spilt the requestCode in 3 part

16bit | 8bit | 8bit

the higher 16bit Android already used,the middle 8bit is to help fragment1 find the fragment2 in his fragmentManager array.the lowest 8bit is the origin requestCode.


For Androidx with Navigation Components using NavHostFragment

Updated answer on September 2, 2019

This issue is back in Androidx, when you are using a single activity and you have nested fragment inside the NavHostFragment, onActivityResult() is not called for the child fragment of NavHostFragment.

To fix this, you need manually route the call to the onActivityResult() of child fragments from onActivityResult() of the host activity.

Here's how to do it using Kotlin code. This goes inside the onActivityResult() of your main activity that hosts the 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) }
}

This will make sure that the onActivityResult() of the child fragments will be called normally.

For Android Support Library

Old answer

This problem has been fixed in Android Support Library 23.2 onwards. We don't have to do anything extra anymore with Fragment in Android Support Library 23.2+. onActivityResult() is now called in the nested Fragment as expected.

I have just tested it using the Android Support Library 23.2.1 and it works. Finally I can have cleaner code!

So, the solution is to start using the latest Android Support Library.


For Main Activity you write OnActivityForResult with Super class like 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());
    } }

For activity Write intent without getActivity() like 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 for fragment

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

}}


I faced the same problem! I solved it by using static ChildFragment in the ParentFragment, like this:

private static ChildFragment mChildFragment;

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

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

}

참고URL : https://stackoverflow.com/questions/13580075/onactivityresult-not-called-in-new-nested-fragment-api

반응형