0

Google's tutorial app (which uses the support library by default) http://developer.android.com/shareables/training/FragmentBasics.zip contains a two-pane layout for -large screens. After successfully compiling and running with min/target SDK at 4/15, I deleted all support library stuff and am trying to modernize to SDK 14/19 as an exercise.

Here is the two-pane layout:

<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.HeadlinesFragment"
              android:id="@+id/headlines_fragment"
              android:layout_weight="1"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

    <fragment android:name="com.example.ArticleFragment"
              android:id="@+id/article_fragment"
              android:layout_weight="2"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

</LinearLayout>

When the ArticleFragment is instantiated, the XML that ships with the tutorial is not wrapped in a ViewGroup:

// ArticleFragment.java
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {

    // If activity recreated (such as from screen rotate), restore
    // the previous article selection set by onSaveInstanceState().
    // This is primarily necessary when in the two-pane layout.
    if (savedInstanceState != null) {
        mCurrentPosition = savedInstanceState.getInt(ARG_POSITION);
    }

    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.article_view, container, false);
}

and

// res/layout/article_view.xml
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/article"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:textSize="18sp" />

This arrangement returns null when something like this is run later: getActivity().findViewById(R.id.article);

I discovered through trial and error and the hierarchy viewer, that the solution to this is to wrap the article_view in a LinearLayout:

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

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/article"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:textSize="18sp" />

</LinearLayout>

Did something change between the support library and the modern Fragment implementations?

Updated to include MainActivity callback and ArticleFragment update code:

// MainActivity.java
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener {

    public void onArticleSelected(int position) {
           ArticleFragment articleFrag = (ArticleFragment)
                getFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            articleFrag.updateArticleView(position);
        }...

// ArticleFragment.java
public void updateArticleView(int position) {
    TextView article = (TextView) getView().findViewById(R.id.article);
    article.setText(Ipsum.Articles[position]);
    mCurrentPosition = position;
}

Updated to include stack trace:

09-18 19:04:45.239    1291-1291/com.example.fragmentbasicsrebuild E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.NullPointerException
            at com.example.fragmentbasicsrebuild.ArticleFragment.updateArticleView(ArticleFragment.java:74)
            at com.example.fragmentbasicsrebuild.MainActivity.onArticleSelected(MainActivity.java:66)
            at com.example.fragmentbasicsrebuild.HeadlinesFragment.onListItemClick(HeadlinesFragment.java:75)
            at android.app.ListFragment$2.onItemClick(ListFragment.java:160)
            at android.widget.AdapterView.performItemClick(AdapterView.java:298)
            at android.widget.AbsListView.performItemClick(AbsListView.java:1100)
            at android.widget.AbsListView$PerformClick.run(AbsListView.java:2788)
            at android.widget.AbsListView$1.run(AbsListView.java:3463)
            at android.os.Handler.handleCallback(Handler.java:730)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5103)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:525)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
jordanpg
  • 6,386
  • 4
  • 46
  • 70

2 Answers2

1

Well, firstly you shouldn't be using getActivity().findViewById(R.id.article) from within the fragment unless you're specifically trying to access something outside of your Fragment's hierarchy (which would typically indicate you're doing something very, very wrong anyway).

What I would imagine is happening is that you're calling getActivity().findViewById() before your Fragment's hierarchy has been added to the Activity. Instead of going through the Activity, simply use your Fragment's View:

@Override
public void onViewCreated(View view, Bundle savedState) {
    // "View view" is the view you returned in onCreateView(),
    // and also the same view that will now be returned by
    // calling getView().

    TextView articleText = (TextView) view.findViewById(R.id.article);
}

As to why wrapping it in another layout causes it to work? No clue. Maybe you did something else different as well and forgot? Shouldn't make any difference, and you definitely don't have to wrap it in a layout if one isn't needed.

Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
  • The `getActivity()` call is in a method invoked by a callback in the main activity, which isn't called until well after `onCreateView()`. When I replace `getActivity()` with `GetView()` I still get the NPE. – jordanpg Sep 18 '14 at 19:10
  • Oddly, this is Google's own tutorial, not my code: http://developer.android.com/training/basics/fragments/index.html – jordanpg Sep 18 '14 at 19:11
  • Can you post what you're doing in that callback? And how you get there? (How you're calling into the Activity; what the activity does to call back to the fragment, etc.)? – Kevin Coppock Sep 18 '14 at 20:32
  • Updated to include relevant `MainActivity` and `ArticleFragment` code. – jordanpg Sep 19 '14 at 18:20
  • And you're saying that call to `getView().findViewById(R.id.article)` fails to locate the view? – Kevin Coppock Sep 19 '14 at 18:23
  • Yes, as well as `getActivity()`. Updated OP with stack trace. – jordanpg Sep 19 '14 at 18:49
0

Static Fragments tend to have some quirks. getActivity().findViewById(R.id.article); returns null probably from the same reason that onCreateView receives a null container (check it out). You are probably better off with dynamically creating the Fragment: (code from http://developer.android.com/guide/components/fragments.html)

FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

See also Android Fragment - onCreateView - container is null and Android Fragment is given a null container in onCreateView()

Community
  • 1
  • 1
J.S.
  • 31
  • 1
  • 2