Null TextView object in a fragment

2019-03-03 08:18发布

问题:

I'm working on dynamically setting a text of TextView in a fragment called 'ChallengeFragment' by creating a method that sets the text. Then, I call the method in FragmentActivity class to update the TextView. However, I am getting error that TextView object is null when the method is called. I am not sure why TextView is null.

Here's the logcat message:

16:36:02.437    4072-4072/eie.android.crunch E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: eie.android.crunch, PID: 4072
java.lang.RuntimeException: Unable to start activity ComponentInfo{eie.android.crunch/eie.android.crunch.ChallengePage}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2325)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
        at android.app.ActivityThread.access$800(ActivityThread.java:151)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5257)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at 

com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
            at eie.android.crunch.ChallengeFragment.setNameText(ChallengeFragment.java:76)
            at eie.android.crunch.ChallengePage.onCreate(ChallengePage.java:50)
            at android.app.Activity.performCreate(Activity.java:5990)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

Here is the Fragment class (ChallengeFragment.java):

public class ChallengeFragment extends Fragment {

private Activity mActivity;
private TextView nameText;
private Handler handler =new Handler();

public ChallengeFragment() {

}

public void onAttach(Activity activity) {
    super.onAttach(activity);
    mActivity = activity;
}

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

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

    LayoutInflater lf = getActivity().getLayoutInflater();
    View v = lf.inflate(R.layout.fragment_challenge, null);
    nameText = (TextView) v.findViewById(R.id.user_name_text_view);        

                            ....

    return v;
}

public void setNameText(String s) {
    nameText.setText(s);
}

public TextView getNameText() {
    return nameText;
}

}

And this is the code for FragmentActivity that updates the text of TextView (ChallengePage.java):

public class ChallengePage extends FragmentActivity {
private FriendChallengeFragment initialFriendHabit;
private ChallengeFragment friendHabit;
private ChallengeFragment myHabit;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_challenge);

    FragmentManager fm = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fm.beginTransaction();

    myHabit = new ChallengeFragment();

    initialFriendHabit = new FriendChallengeFragment();

    myHabit.onAttach(ChallengePage.this);
    initialFriendHabit.onAttach(ChallengePage.this);

    fragmentTransaction
            .replace(R.id.my_habit, myHabit ,"fragment_top")
            .replace(R.id.friend_habit, initialFriendHabit, "fragment_bottom")
            .commit();

    Intent intent = getIntent();
    String challengedUsername = intent.getStringExtra("username");
    if(challengedUsername != null) {
        friendHabit = new ChallengeFragment();
        friendHabit.onAttach(ChallengePage.this);
        fragmentTransaction.replace(R.id.friend_habit, friendHabit, "fragment_bottom");
        friendHabit.setNameText(challengedUsername);
    }

}

}

And this is the layout file that contains the fragment (fragment_challenge.xml):

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:id="@+id/user_name_text_view"
        android:layout_gravity="center_horizontal"
        android:text="User Name"
        android:textSize="30dp"
        android:layout_marginTop="30dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:id="@+id/habit_name_text"
        android:layout_gravity="center_horizontal"
        android:text="Habit Name"
        android:textSize="40dp"
        android:layout_marginTop="20dp"/>

    <ProgressBar
        android:layout_height="wrap_content"
        android:layout_width="312dp"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:id="@+id/progressBar"
        android:layout_marginTop="50dp"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>

I've looked at other stackoverflow questions relating to this but could not find answers.

回答1:

The problem is your fragment's setNameText() method is called before onCreateView() has run, so your TextView has not been initialized yet. You have to wait until later to set the name text.

If you always have the name text when you are creating and adding the fragment, then it's better to pass that as an argument to the fragment and have it set the text of the TextView at the appropriate time. Something like this:

public class ChallengeFragment extends Fragment {

    public static ChallengeFragment newInstance(String name) {
        ChallengeFragment fragment = new ChallengeFragment();
        Bundle args = new Bundle();
        args.putString("username", name);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Bundle args = getArguments();
        if (args != null) {
            String name = args.getString("username");
            nameText.setText(name);
        }
    }

    // everything else
}

A few things to note:

  1. Do not create a constructor. Fragments need to have a default (no argument) constructor so that Android can instantiate them. This newInstance pattern is typically regarded as the best practice.
  2. Use onActivityCreated because at that time you know the Activity is created and that the fragment's view hierarchy has been created.
  3. Fragment arguments persist across configuration changes, so you shouldn't need to do anything special for that.

Also, you should not be calling onAttach() yourself, that's a lifecycle method that the OS calls on your fragment.