10

I keep having an issue with my android app where it is crashing with the following error when swiping between tabs:

09-16 16:19:27.142    4750-4750/com.khackett.runmate E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.khackett.runmate, PID: 4750
    java.lang.IllegalStateException: Content view not yet created
            at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
            at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
            at com.khackett.runmate.ui.MyRunsFragment$1.done(MyRunsFragment.java:167)
            at com.khackett.runmate.ui.MyRunsFragment$1.done(MyRunsFragment.java:135)
            at com.parse.ParseTaskUtils$2$1.run(ParseTaskUtils.java:115)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            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)

This is the MyRunsFragment:

public class MyRunsFragment extends ListFragment {

    protected SwipeRefreshLayout mSwipeRefreshLayout;

    // member variable to store the list of routes the user has accepted
    protected List<ParseObject> mAcceptedRoutes;

    private int MY_STATUS_CODE = 1111;

    // Default constructor for MyRunsFragment
    public MyRunsFragment() {
    }

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

        View rootView = inflater.inflate(R.layout.fragment_my_runs, container, false);

        // Set SwipeRefreshLayout component
        mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeRefreshLayout);
        // Set the onRefreshListener
        mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
        mSwipeRefreshLayout.setColorSchemeResources(
                R.color.swipeRefresh1,
                R.color.swipeRefresh2,
                R.color.swipeRefresh3,
                R.color.swipeRefresh4);

        return rootView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // Retrieve the accepted routes from the Parse backend
        retrieveAcceptedRoutes();
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        // create the message object which is set to the message at the current position
        ParseObject route = mAcceptedRoutes.get(position);

        // String messageType = message.getString(ParseConstants.KEY_FILE_TYPE);

        JSONArray parseList = route.getJSONArray(ParseConstants.KEY_LATLNG_POINTS);
        JSONArray parseListBounds = route.getJSONArray(ParseConstants.KEY_LATLNG_BOUNDARY_POINTS);
        String objectId = route.getObjectId();
        String routeName = route.getString(ParseConstants.KEY_ROUTE_NAME);
        // JSONArray ids = route.getJSONArray(ParseConstants.KEY_RECIPIENT_IDS);

        // Start a map activity to display the route
        Intent intent = new Intent(getActivity(), MapsActivityTrackRun.class);
        intent.putExtra("parseLatLngList", parseList.toString());
        intent.putExtra("parseLatLngBoundsList", parseListBounds.toString());
        intent.putExtra("myRunsObjectId", objectId);
        intent.putExtra("myRunsRouteName", routeName);

        // Start the MapsActivityDisplayRoute activity
        startActivityForResult(intent, MY_STATUS_CODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {

    }

    private void retrieveAcceptedRoutes() {
        // query the routes class/table in parse
        // get messages where the logged in user ID is in the list of the recipient ID's (we only want to retrieve the messages sent to us)
        // querying the message class is similar to how we have been querying users
        ParseQuery<ParseObject> queryRoute = new ParseQuery<ParseObject>(ParseConstants.CLASS_ROUTES);
        // use the 'where' clause to search through the messages to find where our user ID is one of the recipients
        queryRoute.whereEqualTo(ParseConstants.KEY_ACCEPTED_RECIPIENT_IDS, ParseUser.getCurrentUser().getObjectId());
        // order results so that most recent message are at the top of the inbox
        queryRoute.addDescendingOrder(ParseConstants.KEY_CREATED_AT);
        // query is ready - run it
        queryRoute.findInBackground(new FindCallback<ParseObject>() {
            // When the retrieval is done from the Parse query, the done() callback method is called
            @Override
            public void done(List<ParseObject> routes, ParseException e) {
                // dismiss the progress indicator here
                // getActivity().setProgressBarIndeterminateVisibility(false);

                // End refreshing once routes are retrieved
                // done() is called from onResume() and the OnRefreshListener
                // Need to check that its called from the the OnRefreshListener before ending it
                if (mSwipeRefreshLayout.isRefreshing()) {
                    mSwipeRefreshLayout.setRefreshing(false);
                }

                // the list being returned is a list of routes
                if (e == null) {
                    // successful - routes found.  They are stored as a list in messages
                    mAcceptedRoutes = routes;

                    // adapt this data for the list view, showing the senders name

                    // create an array of strings to store the usernames and set the size equal to that of the list returned
                    String[] usernames = new String[mAcceptedRoutes.size()];
                    // enhanced for loop to go through the list of users and create an array of usernames
                    int i = 0;
                    for (ParseObject message : mAcceptedRoutes) {
                        // get the specific key
                        usernames[i] = message.getString(ParseConstants.KEY_SENDER_NAME);
                        i++;
                    }

                    // Create the adapter once and update its state on each refresh
                    if (getListView().getAdapter() == null) {
                        // the above adapter code is now replaced with the following line
                        RouteMessageAdapter adapter = new RouteMessageAdapter(getListView().getContext(), mAcceptedRoutes);

                        // Force a refresh of the list once data has changed
                        adapter.notifyDataSetChanged();

                        // need to call setListAdapter for this activity.  This method is specifically from the ListActivity class
                        setListAdapter(adapter);
                    } else {
                        // refill the adapter
                        // cast it to RouteMessageAdapter
                        ((RouteMessageAdapter) getListView().getAdapter()).refill(mAcceptedRoutes);
                    }
                }
            }
        });
    }

    protected SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            // When list is swiped down to refresh, retrieve the users runs from the Parse backend
            retrieveAcceptedRoutes();
        }
    };

}

And the fragment_my_runs layout file:

<RelativeLayout
    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"
    tools:context=".MainActivity$PlaceholderFragment">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true">

        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:clipToPadding="false"
            android:paddingBottom="@dimen/inbox_vertical_margin"/>

    </android.support.v4.widget.SwipeRefreshLayout>

    <TextView
        android:id="@android:id/empty"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/empty_inbox_label"
        android:textSize="@dimen/default_text_size"/>

</RelativeLayout>

The TabFragmentContainer

public class TabFragmentContainer extends Fragment {

    // Create the FragmentPagerAdapter that will provide and manage tabs for each section.
    public static MyFragmentPagerAdapter myFragmentPagerAdapter;

    public static TabLayout tabLayout;

    // The ViewPager is a layout widget in which each child view is a separate tab in the layout.
    // It will host the section contents.
    public static ViewPager viewPager;

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

        // Inflate tab_layout_fragment_container view and setup views for the TabLayout and ViewPager items.
        View view = inflater.inflate(R.layout.tab_layout_fragment_container, null);

        tabLayout = (TabLayout) view.findViewById(R.id.tabs);

        // Set up the ViewPager with the sections adapter.
        viewPager = (ViewPager) view.findViewById(R.id.viewpager);

        // Instantiate the adapter that will return a fragment for each of the three sections of the main activity
        myFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity(), getChildFragmentManager());

        // Set up the adapter for the ViewPager
        viewPager.setAdapter(myFragmentPagerAdapter);

        // Runnable() method required to implement setupWithViewPager() method
        tabLayout.post(new Runnable() {
            @Override
            public void run() {
                tabLayout.setupWithViewPager(viewPager);
                viewPager.setCurrentItem(1, false);
                // tabLayout.getTabAt(1).select();
            }
        });

        // Return the created View
        return view;
    }

}

The FragmentPagerAdapter:

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    // The context to be passed in when the adapter is created.
    private Context mContext;
    // The number of tabs in the layout.
    public static int numberOfTabs = 3;

    /**
     * Default constructor that accepts a FragmentManager parameter to add or remove fragments.
     *
     * @param context         the context from the activity using the adapter.
     * @param fragmentManager the FragmentManager for managing Fragments inside of the TabFragmentContainer.
     */
    public MyFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
        super(fragmentManager);
        mContext = context;
    }

    /**
     * Method to return the relevant fragment for the selected tab.
     */
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return new MyRunsFragment();
            case 1:
                return new InboxRouteFragment();
            case 2:
                return new FriendsFragment();
        }
        return null;
    }

    /**
     * Method that gets the number of tabs in the layout.
     *
     * @return the number of tabs in the layout.
     */
    @Override
    public int getCount() {
        return numberOfTabs;
    }

    /**
     * Method that returns the title of each tab in the layout.
     */
    @Override
    public CharSequence getPageTitle(int position) {
        Locale locale = Locale.getDefault();
        switch (position) {
            case 0:
                return mContext.getString(R.string.title_section1).toUpperCase(locale);
            case 1:
                return mContext.getString(R.string.title_section2).toUpperCase(locale);
            case 2:
                return mContext.getString(R.string.title_section3).toUpperCase(locale);
        }
        return null;
    }
}

The tab_layout_fragment_container file that contains the ViewPager widget:

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

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/ColorPrimaryPurple"
        app:tabGravity="fill"
        app:tabIndicatorColor="@color/ColorPrimaryPurple"
        app:tabMode="fixed"
        app:tabSelectedTextColor="@color/textColorPrimary"
        app:tabTextColor="@color/pressedPurpleButton">
    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v4.view.ViewPager>

</LinearLayout>

The onCreate() method in my MainActivity:

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

    setContentView(R.layout.activity_main);

    // Initialise the DrawerLayout and NavigationView views.
    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
    mNavigationView = (NavigationView) findViewById(R.id.navigationDrawerMenu);

    // Inflate the first fragment to be displayed when logged into the app.
    mFragmentManager = getSupportFragmentManager();
    mFragmentTransaction = mFragmentManager.beginTransaction();
    mFragmentTransaction.replace(R.id.containerView, new TabFragmentContainer()).commit();

    // Setup click events on the NavigationView items.
    // When an item is selected, replace the tab fragment container with the requested fragment.
    mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(MenuItem menuItem) {
            mDrawerLayout.closeDrawers();
            if (menuItem.getItemId() == R.id.navItemHome) {
                FragmentTransaction tabFragmentContainer = mFragmentManager.beginTransaction();
                tabFragmentContainer.replace(R.id.containerView, new TabFragmentContainer()).commit();
            }
            if (menuItem.getItemId() == R.id.navItemRunHistory) {
                FragmentTransaction runHistoryFragment = mFragmentManager.beginTransaction();
                runHistoryFragment.replace(R.id.containerView, new RunHistoryFragment()).commit();
            }
            if (menuItem.getItemId() == R.id.navItemSettings) {
                FragmentTransaction settingsFragment = mFragmentManager.beginTransaction();
                settingsFragment.replace(R.id.containerView, new SettingsFragment()).commit();
            }
            if (menuItem.getItemId() == R.id.navItemHelp) {
                FragmentTransaction instructionsFragment = mFragmentManager.beginTransaction();
                instructionsFragment.replace(R.id.containerView, new InstructionsFragment()).commit();
            }
            if (menuItem.getItemId() == R.id.navItemMyProfile) {
                FragmentTransaction myProfileFragment = mFragmentManager.beginTransaction();
                myProfileFragment.replace(R.id.containerView, new MyProfileFragment()).commit();
            }
            if (menuItem.getItemId() == R.id.navItemLogOut) {
                // User has selected log out option. Log user out and return to login screen.
                ParseUser.logOut();
                navigateToLogin();
            }
            return false;
        }
    });

    // Set up the Toolbar.
    setupToolbar();
}

I have followed other answers here and added the getListView() functionality to the onViewCreated() method but the problem still persists... Can anyone point out where I might be going wrong?

KvnH
  • 496
  • 1
  • 9
  • 30
  • I want to ask when you get this error? The first time you open up the activity with this ListFragment shown up or you turn to another Fragment then your app gets crash? Because there may be a point of time when your fragment's view is destroyed but your task keeps running ... – Nguyễn Hoài Nam Sep 25 '15 at 02:52

5 Answers5

5

onViewCreated is called immediately after onCreateView, but the super.onViewCreated call is missing, perhaps this is root cause of your issue.

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState); // add this line back in
    // Retrieve the accepted routes from the Parse backend
    retrieveAcceptedRoutes();
}
petey
  • 16,914
  • 6
  • 65
  • 97
  • 2
    `IllegalStateException: Content view not yet created` is thrown when `getView()` returns null – Blackbelt Sep 16 '15 at 16:05
  • getListView or getView? – petey Sep 16 '15 at 16:07
  • 2
    `getListView()` checks if `getView()` is returning null. If `getView()` returns null `Content view not yet created` is thrown – Blackbelt Sep 16 '15 at 16:09
  • @petey already had the `android:id="@android:id/list"` id attribute. I changed it to your recommendation but still the crash occurs. – KvnH Sep 16 '15 at 16:20
  • I'll add the xml layout if it makes any difference – KvnH Sep 16 '15 at 16:22
  • @KvnH yea...yer xml is perfect. see edited/updated answer for new soln – petey Sep 16 '15 at 17:10
  • Thank you @petey Swiping between tabs seems to be a lot more reliable now than it did before (it was crashing on a more regular basis)... however it still crashed out with the same error. Any other ideas? I will give you an upvote – KvnH Sep 16 '15 at 19:04
  • "Swiping between tabs"??? Is your listfragment in a viewpager? might be another question if the log and stacktrace are different. can you post these? – petey Sep 17 '15 at 14:38
  • @petey I have updated the question to include the other relevant files. I am still getting the same error output in the logcat. – KvnH Sep 18 '15 at 11:44
5

Based on these facts:

  • The exception is thrown because there is no root view yet when done() calls getListView().
  • done() is called when the query made by retrieveAcceptedRoutes() gets a response.
  • retrieveAcceptedRoutes is called in multiple places, including the OnRefreshListener mOnRefreshListener, which is registered as the refresh listener in onCreateView() before there is a root view (that is, before onCreateView() returns).

...it is possible for getListView() to be called before there is a root view.

Try moving these 3 statements from onCreateView() to onViewCreated(), so that way the refresh listener can only be called when there is a root view.

    // Set SwipeRefreshLayout component 
    mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeRefreshLayout);
    // Set the onRefreshListener 
    mSwipeRefreshLayout.setOnRefreshListener(mOnRefreshListener);
    mSwipeRefreshLayout.setColorSchemeResources(
            R.color.swipeRefresh1,
            R.color.swipeRefresh2,
            R.color.swipeRefresh3,
            R.color.swipeRefresh4);
cybersam
  • 63,203
  • 6
  • 53
  • 76
  • thanks-you @cybersam - I have moved that code to the `onViewCreated()` which is correct. However I am still getting the same error so I can't say that this was the root cause. Upvote for tidying up the code. – KvnH Sep 19 '15 at 09:35
5

I read your question again then I guess that:

  • Your ListFragment is destroyed while your background task keeps running. So when it's done, your callback would like to update the ListView which is no longer alive.

  • Actually, viewPager.setOffscreenPageLimit(3); may do the trick, but it's not a good practice. It forces your ViewPager to create and store more Fragments in memory which is not necessary. You can solve this without doing so.

What you should do: one of the following two practice should be fine, or both:

  • Destroy your task in your onPause or whatever lifecycle method, before your onDestroyView.

  • Exclude the code where you update your ListView inside your done() method. Make it a local method where you will check your ListView carefully, and there, you should ask your update process to run on UI thread to avoid any threading problem. Make sure to check if your getView() is not null (but not your getListView(), since it throws Exception if getView() returns null).

I recommend you to use both of them to make sure: your view is still useable and you don't waste your resource when running task in invisible fragment. Don't forget that by default, once your fragment is invisible, it is considered to be destroyed (not always, for example ViewPager keep reference of 2 fragments, but keep in mind that case).

miken32
  • 42,008
  • 16
  • 111
  • 154
Nguyễn Hoài Nam
  • 1,130
  • 1
  • 9
  • 20
4

remove all these imports:

import com.yourName.runmate.R;

Then resync your gradle and rebuild your project.

enter image description here

Also see here:
"cannot resolve symbol R" in Android Studio

edit

Your first obvious mistake in your Main is

mFragmentManager = getSupportFragmentManager();

should be:

mFragmentManager = getFragmentManager();

or change your Main activity to:

MainActivity extends FragmentActivity to make use of the support fragment manager.


You have a lot of unnecessary code in your question, majority of comments can be removed and imports for the purpose of this question.

What I have come up with is there is no activity, being used. The ListFragment needs to be attached to an Activity or you are trying to call that activity view before it is created.

java.lang.IllegalStateException: Content view not yet created
            at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
            at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)

If you are using Main, then you are not pulling them together well, from what I can see.

Firstly:

Take everything out of your onCreate and onCreateView (for all fragments) except the view inflater.

Place all the extra code into either onViewCreated or onActivityCreated. That way no methods can be called on a null view, as these are called after it is created.

Secondly, you need to sort out your activities and with what you're exactly trying to achieve.

You want a page viewer and a fragmentlist. The pageviewer needs to be associated with an activity, or activity fragment, not a fragment. Otherwise there is no view to attach the pageviewer pages to.

Use a FragmentActivity not a Fragment. To be the activity you run your ListFragment from.

public class TabFragmentContainer extends FragmentActivity {

    MyPageAdapter pageAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tab_layout_fragment_container); // change to view layout.

        // Instantiate the adapter that will return a fragment for each of the three sections of the main activity
        myFragmentPagerAdapter = new MyFragmentPagerAdapter(getFragmentManager(), getFragments());

        tabLayout = (TabLayout) view.findViewById(R.id.tabs);

         // Set up the ViewPager with the sections adapter.
        viewPager = (ViewPager) view.findViewById(R.id.viewpager);
                // Set up the adapter for the ViewPager
        viewPager.setAdapter(myFragmentPagerAdapter);

    }
}

I would suggest putting this into your ListFragment, to ensure your activity is created. You will need to move most of your code from your onCreate methods and put them in onViewCreated or onActivityCreated

@Override
public void onActivityCreated(Bundle savedInstanceState) {
  super.onActivityCreated(savedInstanceState);
  ArrayAdapter adapter = ArrayAdapter.createFromResource(getActivity(), R.layout.my_listview)layout, android.R.layout.simple_list_item_1);

  setListAdapter(adapter);
  getListView().setOnItemClickListener(this);
}

This code is just a guide, you'll need to tweak it.

Let me know if this helps.

These Q&As are excellent.

Content view not yet created

android Illegal state exception content view not yet create?


Fragment same principles applies to viewpager fragments ViewPager

Community
  • 1
  • 1
  • Can you tell me where the `view` object is created that is used in the `TabFragmentContainer` `onCreate()` method? Also there is a 'cannot resolve method' being produced by the `getFragment()` method in `onCreate()` - I've tried replacing it with `getChildFragmentManager()` but still the error occurs - should this be something else? – KvnH Sep 19 '15 at 09:32
  • I changed to ` getFragmentManager()` but its bringing up import errors throughout the rest of the code - I've tried to change these imports (mainly from `support.v4.app...` to `app...` but it just seems to be creating more issues. Also, I still haven't managed to implement the steps you outlined in the original answer - changing to `FragmentActivity` etc because I still get the issues I mentioned. As you can probably tell I am new to Android and coding so there are a lot of things which I'm unsure about but I really appreciate all the help so far. – KvnH Sep 19 '15 at 11:11
  • I have removed the imports, resynced with gradle and rebuilt the project... `cannot resolve symbol 'R'` is now appearing where `R.id...` is called - should I reimport it? – KvnH Sep 19 '15 at 12:04
  • Sorry... when I rebuild I get the following in Messages Gradle Build - `Error:(73, 44) error: package R does not exist` – KvnH Sep 19 '15 at 12:11
2

Try to declare:

viewPager = (ViewPager) view.findViewById(R.id.viewpager); 
viewPager.setAdapter(myFragmentPagerAdapter);  
myFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity(), getChildFragmentManager());

before:

    tabLayout = (TabLayout) view.findViewById(R.id.tabs);
    tabLayout.post(new Runnable() {
            @Override
            public void run() {
                tabLayout.setupWithViewPager(viewPager);
                viewPager.setCurrentItem(1, false);
                // tabLayout.getTabAt(1).select();
            }
        });

        // Return the created View
        return view;
Stanojkovic
  • 1,612
  • 1
  • 17
  • 24