简体   繁体   中英

IndexOutOfBoundsException crashing android app

I am clicking an item from a ViewList of the previous activity. This activity is supposed to take that object and display said object's characteristics. When I click on the viewList the app crashes. Here is the logcat:

04-16 19:37:09.183 22696-22696/cs4326.cook4me E/AndroidRuntime: FATAL EXCEPTION: main
Process: cs4326.cook4me, PID: 22696
java.lang.RuntimeException: Unable to start activity ComponentInfo{cs4326.cook4me/cs4326.cook4me.RecipeInstructActivity}: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
     at android.app.ActivityThread.-wrap12(ActivityThread.java)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
     at android.os.Handler.dispatchMessage(Handler.java:102)
     at android.os.Looper.loop(Looper.java:154)
     at android.app.ActivityThread.main(ActivityThread.java:6119)
     at java.lang.reflect.Method.invoke(Native Method)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
        Caused by: java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
     at java.util.ArrayList.add(ArrayList.java:457)
     at cs4326.cook4me.RecipeInstructActivity.onCreate(RecipeInstructActivity.java:45)
     at android.app.Activity.performCreate(Activity.java:6679)
     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726) 
     at android.app.ActivityThread.-wrap12(ActivityThread.java) 
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477) 
     at android.os.Handler.dispatchMessage(Handler.java:102) 
     at android.os.Looper.loop(Looper.java:154) 
     at android.app.ActivityThread.main(ActivityThread.java:6119) 
     at java.lang.reflect.Method.invoke(Native Method) 
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

Here is the corresponding activity code (error says at line 45):

package cs4326.cook4me;

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;

public class RecipeInstructActivity extends FragmentActivity {

    private static final String TAG = "RecipeInstructActivity";
    /**
     * Identifier for the example fragment.
     */
    public static final int FRAGMENT_COMPLETE = 1;
    public static final int FRAGMENT_STEPPED = 2;

    /**
     * The adapter definition of the fragments.
     */
    private FragmentPagerAdapter _fragmentPagerAdapter;

    /**
     * The ViewPager that hosts the section contents.
     */
    private ViewPager _viewPager;

    /**
     * List of fragments.
     */
    private List<Fragment> _fragments = new ArrayList<>();

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        Log.d(TAG, "onCreate:");
        super.onCreate(savedInstanceState);

        this.setContentView(R.layout.activity_main);

        // Each fragment to our list.
        this._fragments.add(FRAGMENT_COMPLETE, new CompleteInstructionFragment());
        this._fragments.add(FRAGMENT_STEPPED, new SteppedInstructionFragment());

        // Setup the fragments, defining the number of fragments, the screens and titles.
        this._fragmentPagerAdapter = new FragmentPagerAdapter(this.getSupportFragmentManager()){
            @Override
            public int getCount() {
                return RecipeInstructActivity.this._fragments.size();
            }
            @Override
            public Fragment getItem(final int position) {
                return RecipeInstructActivity.this._fragments.get(position);
            }
            @Override
            public CharSequence getPageTitle(final int position) {
                // Define titles for each fragment.
                switch (position) {
                    case FRAGMENT_COMPLETE:
                        return "Complete Recipe";
                    case FRAGMENT_STEPPED:
                        return "Step-by-Step";
                    default:
                        return null;
                }
            }
        };

        this._viewPager = (ViewPager) this.findViewById(R.id.pager);
        this._viewPager.setAdapter(this._fragmentPagerAdapter);

        // Set the default fragment.
        this.openFragment(FRAGMENT_COMPLETE);
    }

    /**
     * Open the specified fragment.
     * @param fragment
     */
    public void openFragment(final int fragment) {
        this._viewPager.setCurrentItem(fragment);
    }

    /**
     * Get the fragment object for the specified fragment.
     * @param fragment
     * @return
     */
    public Fragment getFragment(final int fragment) {
        return this._fragments.get(fragment);
    }
}

Here is the code from the parent viewList activity:

package cs4326.cook4me;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;

import java.util.Comparator;
import java.util.TreeSet;

public class RecipesActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
    private static final String TAG = "RecipesActivity";
    private DatabaseReference databaseReference;
    private ListView mainListView ;
    private ArrayAdapter<String> listAdapter ;
    private TreeSet<Recipe> allRecipeData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recipes);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        //Initialize reference to database
        databaseReference = FirebaseDatabase.getInstance().getReference();
        //Initialize recipe data set
        allRecipeData = new TreeSet<Recipe>(new Comparator<Recipe>() {
            @Override
            public int compare(Recipe o1, Recipe o2) {
                return o1.getTitle().compareToIgnoreCase(o2.getTitle());
            }
        });
        // Create ArrayAdapter
        listAdapter = new ArrayAdapter<String>(this, R.layout.row_layout);
        //Create query for recipes
        Query recipesReference = databaseReference.child("recipes").orderByChild("title");
        recipesReference.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                // Get Recipe object and use the values to update the UI
                Recipe recipe = dataSnapshot.getValue(Recipe.class);
                listAdapter.add(recipe.getTitle());
                allRecipeData.add(recipe);
                Log.d(TAG, "loadRecipe:onChildAdded");
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String previousName) {
                // Get Recipe object and use the values to update the UI
                Recipe recipe = dataSnapshot.getValue(Recipe.class);
                listAdapter.remove(previousName);
                listAdapter.add(recipe.getTitle());
                Log.d(TAG, "loadRecipe:onChildChanged");
            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                // Get Recipe object and use the values to update the UI
                Recipe recipe = dataSnapshot.getValue(Recipe.class);
                listAdapter.remove(recipe.getTitle());
                allRecipeData.remove(recipe);
                Log.d(TAG, "loadRecipe:onRemoved");
            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String olden) {
                // Get Recipe object and use the values to update the UI
                Recipe recipe = dataSnapshot.getValue(Recipe.class);
                listAdapter.remove(olden);
                listAdapter.add(recipe.getTitle());
                Log.d(TAG, "loadRecipe:onChildMoved");
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                // Getting Recipe failed, log a message
                Log.w(TAG, "loadRecipe:onCancelled", databaseError.toException());
            }
        });

        // Find the ListView resource.
        mainListView = (ListView) findViewById( R.id.listViewRecipes );
        //Add list items!
        mainListView.setAdapter( listAdapter );

        //TODO: Make it so clicking / tapping on each item redirects to Recipe
        mainListView.setOnItemClickListener(this);


        //Floating login button
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent transfer = new Intent(RecipesActivity.this, LoginActivity.class);
                startActivity(transfer);
            }
        });

        //If you type, it will automatically start searching
        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
        //Floating search button
        FloatingActionButton floatingSearch = (FloatingActionButton) findViewById(R.id.search_recipes);
        floatingSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onSearchRequested();
            }
        });

        //Redirects back to Main Menu if press up button
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        Log.d(TAG, "You clicked " + listAdapter.getItem(position));
        String nameOf = listAdapter.getItem(position);
        Recipe selectedRecipe = new Recipe();
        // Then you start a new Activity via Intent
        Intent specialTransfer = new Intent(RecipesActivity.this, RecipeInstructActivity.class);
        for (Recipe r : allRecipeData) {
            if (r.getTitle().equals(nameOf)) {
                selectedRecipe = r;
            }
        }
        //Pass selected recipe 
        specialTransfer.putExtra("recipe_object", selectedRecipe);
        startActivity(specialTransfer);

    }

}

Here is the AndroidManifest.XML:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cs4326.cook4me">

    <uses-permission android:name="android.permission.INTERNET" />

    <!-- To auto-complete the email text field in the login form with the user's emails -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light">
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".LoginActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.MainActivity" />
        </activity>

        <activity
            android:name=".RegisterActivity"
            android:label="Register User"
            android:parentActivityName=".LoginActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.LoginActivity" />
        </activity>

        <activity
            android:name=".ProfileActivity"
            android:label="User profile"
            android:parentActivityName=".LoginActivity" />
        <activity
            android:name=".SettingsActivity"
            android:label="@string/title_activity_settings"
            android:parentActivityName=".MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.MainActivity" />
        </activity>
        <!--
        <activity
            android:name=".RecipeFragment"
            android:label="@string/title_activity_recipe_fragment"
            android:theme="@style/AppTheme.NoActionBar" />
        <activity
            android:name=".ProfileFragment"
            android:label="@string/title_activity_profile_fragment"
            android:theme="@style/AppTheme.NoActionBar"></activity>
        -->
        <activity
            android:name=".RecipesActivity"
            android:label="@string/title_activity_recipes"
            android:parentActivityName=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.MainActivity" />
            <meta-data android:name="android.app.default_searchable"
                android:value=".SearchRecipeActivity" />
        </activity>
        <activity
            android:name=".CookingTerminologyActivity"
            android:label="@string/title_activity_cooking_terminology"
            android:parentActivityName=".MainActivity"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.MainActivity" />
        </activity>
        <activity
            android:name=".TermActivity"
            android:label="@string/title_activity_term"
            android:parentActivityName=".CookingTerminologyActivity"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.CookingTerminologyActivity" />
        </activity>

        <activity
            android:name=".SearchRecipeActivity"
            android:label="@string/title_activity_search_recipe"
            android:parentActivityName=".RecipesActivity"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="cs4326.cook4me.RecipesActivity" />
            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            <meta-data android:name="android.app.searchable"
                android:resource="@xml/searchable"/>
        </activity>

        <activity
            android:name=".RecipeInstructActivity"
            android:parentActivityName=".RecipesActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="cs4326.cook4me.RecipeInstructActivity" />

        </activity>
    </application>

</manifest>

Problem 1: Static initialisation

private List<Fragment> _fragments = new ArrayList<>(); //be careful doing this!

Field initialisation doesn't work well in Android Activities and Fragments because their lifecycles are controlled. While in this case it's not causing the problem, you can run into trouble if you use findViewById(int id) or other such methods in field initialization. Until the lifecycles of Activities and Fragments are really clear, it may be better just to avoid and to put the code inside the onCreate() or onResume() callbacks as necessary.

Problem 2: Rolling our own FragmentManager

In this code, Fragments are being cached inside a list. There is no need for this - it's the job of the FragmentManager to cache fragments.

Also, the method FragmentPagerAdapter#getItem() should be used for instantiation, not retrieving from a list. It's not really your fault - I think the method is poorly named. It really means something more like "createItem()" and it should look something more like this:

    this._fragmentPagerAdapter = new FragmentPagerAdapter(this.getSupportFragmentManager()){
        @Override
        public int getCount() {
            return RecipeInstructActivity.this._fragments.size();
        }
        @Override
        public Fragment getItem(final int position) {
            switch (position) {
                case FRAGMENT_COMPLETE:
                    return new CompleteInstructionFragment();
                case FRAGMENT_STEPPED:
                    return new StepByStepFramgment();
        }

It's best to leave everything to the ViewPager if possible. If you can't and you need to manipulate Fragments yourself, then when you are adding Fragments use the overload that lets you add a tag to the Fragment when you complete a transaction and then when you need a handle on the Fragment you can use findFragmentByTag to retrieve it.

Thanks to suggestions, I changed directions and went a different route with the initialization and adapter. This is the resulting code that worked for me to create an activity that encompasses a tabbed layout.

Main Activity:

package cs4326.cook4me;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.net.Uri;
import android.support.design.widget.TabLayout;
import android.support.v4.app.FragmentManager;


public class RecipeInstructActivity extends AppCompatActivity implements
        CompleteInstructionFragment.OnFragmentInteractionListener,
        SteppedInstructionFragment.OnFragmentInteractionListener {

    private static final String TAG = "RecipesActivity";
    private SectionsPagerAdapter mSectionsPagerAdapter;

    /**
     * The ViewPager that hosts the section contents.
     */
    private ViewPager mViewPager;

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

        this.setContentView(R.layout.activity_recipe_instruct);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        //setActionBar();
        setSupportActionBar(toolbar);

        // Create the adapter that will return a fragment for each of the three
        // primary sections of the activity.
        mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(mViewPager);
    }

    @Override
    public void onFragmentInteraction(Uri uri) {

    }

    /**
     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to
     * one of the sections/tabs/pages.
     */
    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            Fragment fragment = null;
            switch (position) {
                case 0: {
                    fragment = new CompleteInstructionFragment();
                    break;
                }
                case 1: {
                    fragment = new SteppedInstructionFragment();
                    break;
                }
            }

            return fragment;
        }

        @Override
        public int getCount() {
            // Show 2 total pages.
            return 2;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            switch (position) {
                case 0:
                    return "Full Recipe";
                case 1:
                    return "Step-by-Step";
            }
            return null;
        }
    }
}

Fragment #1:

package cs4326.cook4me;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by nathan1324 on 4/16/17.
 */

public class CompleteInstructionFragment extends Fragment {

    private OnFragmentInteractionListener mListener;

    public CompleteInstructionFragment() {
        // required empty constructor
    }

    //Recipe robj= getIntent().getParcelableExtra("recipe_object");

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {

        }
    }

    public static CompleteInstructionFragment newInstance() {
        CompleteInstructionFragment fragment = new CompleteInstructionFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

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

        // view created from XML layout
        View view = inflater.inflate(R.layout.fragment_completeinstruction, container, false);

//        Create
//                View
//                        Here

        // could add other customization here for loyout components later
        return view;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(Uri uri);
    }

}

Fragment #2:

package cs4326.cook4me;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by nathan1324 on 4/16/17.
 */

public class SteppedInstructionFragment extends Fragment {

    private OnFragmentInteractionListener mListener;

    public SteppedInstructionFragment() {
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    public static SteppedInstructionFragment newInstance() {
        SteppedInstructionFragment fragment = new SteppedInstructionFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
        }
    }

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

        // view created from XML layout
        View view = inflater.inflate(R.layout.fragment_steppedinstruction,container, false);



        // could add other customization here for layout components later


        return view;
    }

    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }


    public interface OnFragmentInteractionListener {
        void onFragmentInteraction(Uri uri);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM