마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

액티비티->프래그먼트 객체 전달과 프래그먼트 매니저에 대한 질문.

0 추천

음..어제 액티비티->프래그먼트 객체전달에서 프래그먼트에서 getArgument() 후 getParcelable()시

계속 null object reference에러가 나서 질문드렸습니다. 디버깅을해도 값은 제대로 들어와있고

구글링해서 이 예제 저 예제봐도 해당 보내고 받는 코드에는 이상이 없는것같아 프로젝트에

이 코드 저 코드 꼬였나 싶어 새로 프로젝트를 만든후에 간단하게 액티비티, 프래그먼트, 데이터 클래스만

따로 간단하게 만들어 테스트 했습니다.

근데 거기서도 계속 에러가나더라구요. 객체보내는게 문제인가 싶어서 객체를 보내지 않고

문자열만 보내봤습니다. putString, 해서말이죠. 근데도 에러났습니다. 

정말 이상하다 싶어서 이것저것 건드리다가

저는 예상하지 못했던 곳에서 에러가 났는데요 메인 xml 레이아웃파일에 있었습니다.

코드입니다.

MainActivity.java

public class MainActivity extends AppCompatActivity {
    SampleData data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        data = new SampleData("꾼", "20201031");

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.showfragment, ShowFragment.newInstance(data));
        fragmentTransaction.commit();
    }
}

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <fragment
        android:id="@+id/showfragment"
        android:name="com.example.test2.ShowFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

 

ShowFragment.java

public class ShowFragment extends Fragment {
    SampleData sampleData;
    TextView textView;

    public static ShowFragment newInstance(SampleData data) {
        Bundle args = new Bundle();
        ShowFragment fragment = new ShowFragment();
        args.putParcelable("key", data);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle bundle = this.getArguments();
        sampleData = bundle.getParcelable("key");
        Log.d("확인용", "title : " + sampleData.title);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container2,
                             Bundle savedInstanceState) {

        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_show, container2, false);
        textView = rootView.findViewById(R.id.title);
        textView.setText(sampleData.title);

        return rootView;
    }
}

 

fragment_show.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#73E0ED"
    android:orientation="vertical"
    tools:context=".ShowFragment">

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#CC1D1D"
        android:text="Show Fragment"
        android:textSize="50dp" />

</LinearLayout>

SampleData.java

public class SampleData implements Parcelable {
    String title;
    String date;

    public SampleData(String title, String date) {
        this.title = title;
        this.date = date;
    }
    public SampleData(Parcel src) {
        title = src.readString();
        date = src.readString();
    }

    public static final Creator<SampleData> CREATOR = new Parcelable.Creator<SampleData>() {
        @Override
        public SampleData createFromParcel(Parcel in) {
            return new SampleData(in);
        }
        @Override
        public SampleData[] newArray(int size) {
            return new SampleData[size];
        }
    };
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(title);
        dest.writeString(date);
    }
}


이게 에러나는 코드인데 activity_main.xml의 fragment 태그에서 에러가 났습니다.

에러코드는 대강

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.test2/com.example.test2.MainActivity}: android.view.InflateException: Binary XML file line #16 in com.example.test2:layout/activity_main: Binary XML file line #16 in com.example.test2:layout/activity_main: Error inflating class fragment
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)

        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: android.view.InflateException: Binary XML file line #16 in com.example.test2:layout/activity_main: Binary XML file line #16 in com.example.test2:layout/activity_main: Error inflating class fragment
     Caused by: android.view.InflateException: Binary XML file line #16 in com.example.test2:layout/activity_main: Error inflating class fragment
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Parcelable android.os.Bundle.getParcelable(java.lang.String)' on a null object reference
        at com.example.test2.ShowFragment.onCreate(ShowFragment.java:31)

이러한데 첫번째 줄의 xml 파일 에러코드를 봤음에도 이게 뭐가 문제지 싶어 그냥 넘어간건데 문제였네요..

문제라고 생각안했기에 null object reference 에러에만 너무 집중했습니다

xml파일의 fragment코드가 뭐가 문제지 하다가 fragment를

<FrameLayout
        android:id="@+id/showfragment"
        android:name="com.example.test2.ShowFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

대충 이렇게 아무런 레이아웃으로 변경해주고 실행하니 실행이 되어 놀랐습니다

xml파일의 fragment가 원인인줄 몰랐거든요... 근데 말씀드리면 아직도 뭐가 문제인지 잘 모르겠습니다.

저는 공부하면서 또 프래그먼트를 사용하면서 저기 fragment 태그를 써왔었는데..저게 왜 문제가 되는지 잘

이해를 못하겠습니다..

xml파일에서 fragment 태그로 프래그먼트를 생성하면 데이터가 전혀보내지질않던데 왜그런지 이유를 모르

겠습니다..

그래서

메인액티비티의 코드를 프래그먼트 관련 코드를 다지우고..정말 그냥 생 액티비티만 두고

메인 xml에 fragment 태그만 두고 프래그먼트를 연결 하면 당연 프래그먼트는 뜨기는하지만

태그에서 fragment를 사용하고 데이터를 보내면 왜 에러가나는지..

그래서 여기서 드는 질문은

 

1.프래그먼트매니저가 프래그먼트를 관리하기때문에 프래그먼트는 프래그먼트 매니저가 주요한 걸로 알고있는데...그렇다면 메인액티비티에서 아무코드도 작성하지않고 메인xml에서 fragment 태그만 넣었을때는 프래그먼트매니저도 없는데 어떻게 프래그먼트가 화면상에 보여지는지..? 액티비티 내부에 기본적으로 프래그먼트 매니저가 저장되어 있나요? 그래서 fragment 태그를 사용하지않고 framelayout을 사용했을때는 따로 메인액티비티에서 프래그먼트 매니저를 이용해 화면에 띄워줘야했던건가요?

2.메인 xml에 fragment를 넣고 메인 액티비티에서 프래그먼트 매니저를 사용했을때(본문 코드)는 왜 getArgument에러가 났는지..

 

3.fragment 태그를 사용해 프래그먼트를 화면상에 보여줄시에는 액태비티->프래그먼트로의 객체전달이 불가능한가요? 프로젝트를 간단하게 만들어 실험했을때는 xml태그에서 fragment태그를 이용해 프래그먼트를 만들었을때 액티비티에서 프래그먼트로 값을보낸경우 이상하게 null object 에러가 납니다..객체뿐만아니라 단순 일반데이터도 그런것같아요

 

긴글이지만 읽어보시고 답변좀 해주시면 감사하겠습니다!

 

 

 

codeslave (3,940 포인트) 님이 2020년 11월 1일 질문
codeslave님이 2020년 11월 1일 수정

2개의 답변

0 추천
 
채택된 답변

참고로 개발자 문서에 나온 내용입니다.
 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the onCreateView() method for each one, to retrieve each fragment's layout. The system inserts the View returned by the fragment directly in place of the <fragment> element.

시스템이 액티비티의 레이아웃을 생성할 때, 레이아웃에 정의된 각각의 프레그먼트를 초기화하는데, 이 때 각 프레그먼트의 onCreateView 메소드를 호출해서 각 프레그먼트의 레이아웃을 가져온다. 시스템은 이 메소드가 리턴하는 뷰를 바로 <fragment> 에 넣어준다.

즉, <fragment>를 사용할 경우 안드로이드 시스템이 알아서 프레그먼트를 생성해서 초기화 해줍니다.

spark (224,800 포인트) 님이 2020년 11월 1일 답변
codeslave님이 2020년 11월 3일 채택됨
0 추천

그렇군요. 전 <fragment> 를 아예 안쓰는 쪽이라. 대신에  <FrameLayout>이나 <FragmentContainer>를 사용합니다.  <fragment> 는 static하게 fragment를 xml에 넣을 때 사용하는 것으로 압니다.  프레그먼트의 전환을 해야한다면 FragmentManger의 FragmentTransaction을 통해 해야 하기 때문에 <FrameLayout>나  <FragmentContainer>를 XML에 정의하고 코드를 통해 프레그먼트를 add, replace, remove하는 방법을 사용하는 것이 일반적입니다. 

그리고 모든 프레그먼트는 FragmentManager가 관리를 하는 것으로 압니다. 백스택이란게 있는게 말 그대로 프레그먼트를 관리하는 스택입니다. 따라서 어떤 프레그먼트를 화면에 보여주어야 할지 알 수 있는 것이죠. 따라서 백스택에서 프레그먼트를 제거하면 더이상 화면에 보이지 않습니다. 이게 백버튼을 누를 때 일어나는 일이구요. <fragment>도 백스택에 존재하겠죠. 다만 액티비티가 백스택에서 제거될 때까지 남아 있습니다.

그리고 테스트는 안해봤지만 <fragment>를 사용하셨을 경우에는 Fragment를 동적으로 만들지 말아야 할 것 같습니다. 왜냐하면 해당 프레그먼트는 이미 백스택에 존재하기 때문이죠. 따라서 데이터를 전달하는 방법은 백스택에 있는 프레그먼트를 찾아서 데이터를 전달해 주면 되지 않을까 생각합니다.

MovieFragment movieFragment = (MovieFragment) findFragmentById(fragment_id);
if (movieFragment == null) return;

movieFragment.setMovie(movie);
spark (224,800 포인트) 님이 2020년 11월 1일 답변
...