Question

This might be a bit of a lengthy post so my apologies in advance.

I'm using the master/detail flow to display a list of items and when clicking on it it opens the detail view. The items are loaded from a webservice. It works great on the tablet with the fragments but on the phone it keeps crashing. It can display the item detail (CheatViewPageIndicator.java) properly but when I use the "up" button on the top left in the action bar to return to the parent activity (CheatListActivity.java) the app keeps crashing with a nullpointer exception. I think I'm loading the data from the webservice at the wrong location and that's why it crashes. I'm going to write my code here and hopefully someone can give me an advice how I would have to do this the correct way.

(I've trimmed the classes a bit to shorten the post a bit.)

The "master" activity:

public class CheatListActivity extends FragmentActivity implements CheatListFragment.Callbacks, ReportCheatDialogListener, RateCheatDialogListener {

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

        settings = getSharedPreferences(Konstanten.PREFERENCES_FILE, 0);
        editor = settings.edit();

        cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 

        cheatProgressDialog = ProgressDialog.show(this, getString(R.string.please_wait) + "...", getString(R.string.retrieving_data) + "...", true);

        handleIntent(getIntent());

        if (findViewById(R.id.cheat_detail_container) != null) {
            // The detail container view will be present only in the
            // large-screen layouts (res/values-large and
            // res/values-sw600dp). If this view is present, then the
            // activity should be in two-pane mode.
            mTwoPane = true;

            // In two-pane mode, list items should be given the
            // 'activated' state when touched.
            ((CheatListFragment) getSupportFragmentManager().findFragmentById(R.id.cheat_list)).setActivateOnItemClick(true);
        }
        cheatProgressDialog.dismiss();
        // TODO: If exposing deep links into your app, handle intents here.

    }

    private void handleIntent(final Intent intent) {

        new Thread(new Runnable() {

            @Override
            public void run() {
                gameObj = new Gson().fromJson(intent.getStringExtra("gameObj"), Game.class);

                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        getActionBar().setDisplayHomeAsUpEnabled(true);
                        getActionBar().setTitle(gameObj.getGameName());
                        getActionBar().setSubtitle(gameObj.getSystemName());
                    }

                });
                try {
                    if (cm.getActiveNetworkInfo() != null) {
                        if (member == null) {
                            cheats = Webservice.getCheatList(gameObj, 0);
                        } else {
                            cheats = Webservice.getCheatList(gameObj, member.getMid());
                        }
                        cheatsArrayList = new ArrayList<Cheat>();

                        if (cheats != null) {
                            for (int j = 0; j < cheats.length; j++) {
                                cheatsArrayList.add(cheats[j]);
                            }
                        } else {
                            Log.e("CheatListActivity()", "Webservice.getCheatList() == null");
                        }

                        for (int i = 0; i < cheats.length; i++) {
                            Log.d("cheats", cheats[i].getCheatTitle());
                        }

                        gameObj.setCheats(cheats);

                        // Put game object to local storage for large games like
                        // Pokemon
                        editor.putString(Konstanten.PREFERENCES_TEMP_GAME_OBJECT_VIEW, new Gson().toJson(gameObj));
                        editor.commit();
                    } else {
                        Log.e("CheatTitleList:getCheats()", "No Network");
                        Toast.makeText(CheatListActivity.this, R.string.no_internet, Toast.LENGTH_SHORT).show();
                    }

                } catch (Exception ex) {
                    Log.e(getClass().getName(), "Error executing getCheats()", ex);
                }
            }
        }).start();

    }

    public Cheat[] getCheatsForFragment(final Intent intent) {

        gameObj = new Gson().fromJson(intent.getStringExtra("gameObj"), Game.class);

        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                getActionBar().setDisplayHomeAsUpEnabled(true);
                getActionBar().setTitle(gameObj.getGameName());
                getActionBar().setSubtitle(gameObj.getSystemName());
            }

        });

        try {
            if (cm.getActiveNetworkInfo() != null) {
                if (member == null) {
                    cheats = Webservice.getCheatList(gameObj, 0);
                } else {
                    cheats = Webservice.getCheatList(gameObj, member.getMid());
                }
                cheatsArrayList = new ArrayList<Cheat>();

                if (cheats != null) {
                    for (int j = 0; j < cheats.length; j++) {
                        cheatsArrayList.add(cheats[j]);
                    }
                } else {
                    Log.e("CheatListActivity()", "Webservice.getCheatList() == null");
                }

                for (int i = 0; i < cheats.length; i++) {
                    Log.d("cheats", cheats[i].getCheatTitle());
                }

                gameObj.setCheats(cheats);

                // Put game object to local storage for large games like Pokemon
                editor.putString(Konstanten.PREFERENCES_TEMP_GAME_OBJECT_VIEW, new Gson().toJson(gameObj));
                editor.commit();
            } else {
                Log.e("CheatTitleList:getCheats()", "No Network");
                Toast.makeText(this, R.string.no_internet, Toast.LENGTH_SHORT).show();
            }

        } catch (Exception ex) {
            Log.e(getClass().getName(), "Error executing getCheats()", ex);
        }

        return cheats;
    }

    /**
     * Callback method from {@link CheatListFragment.Callbacks} indicating that
     * the item with the given ID was selected.
     */
    @Override
    public void onItemSelected(int id) {
        if (mTwoPane) {
            // In two-pane mode, show the detail view in this activity by
            // adding or replacing the detail fragment using a
            // fragment transaction.

            visibleCheat = cheats[id];

            cheatForumFragment = new CheatForumFragment();
            cheatDetailMetaFragment = new CheatDetailMetaFragment();

            // VIEW FOR TABLETS
            Bundle arguments = new Bundle();
            arguments.putInt(CheatDetailTabletFragment.ARG_ITEM_ID, id);
            arguments.putString("cheatObj", new Gson().toJson(cheats[id]));
            arguments.putString("cheatForumFragment", new Gson().toJson(cheatForumFragment));
            arguments.putString("cheatDetailMetaFragment", new Gson().toJson(cheatDetailMetaFragment));

            cheatDetailFragment = new CheatDetailTabletFragment();
            cheatDetailFragment.setArguments(arguments);
            getSupportFragmentManager().beginTransaction().replace(R.id.cheat_detail_container, cheatDetailFragment).commit();

        } else {
            // In single-pane mode, simply start the detail activity
            // for the selected item ID.
            // Intent detailIntent = new Intent(this, YyyDetailActivity.class);
            // detailIntent.putExtra(YyyDetailFragment.ARG_ITEM_ID, id);
            // startActivity(detailIntent);

            editor.putInt(Konstanten.PREFERENCES_PAGE_SELECTED, id);
            editor.commit();

            // Using local Preferences to pass data for large game objects
            // (instead of intent) such as Pokemon
            Intent explicitIntent = new Intent(CheatListActivity.this, CheatViewPageIndicator.class);
            explicitIntent.putExtra("selectedPage", id);
            explicitIntent.putExtra("layoutResourceId", R.layout.activity_cheatview_pager);
            explicitIntent.putExtra("pageIndicatorColor", Konstanten.CYAN_DARK);
            startActivity(explicitIntent);

        }
    }   
}

And the fragment for the listview.

public class CheatListFragment extends ListFragment {

    /**
     * The serialization (saved instance state) Bundle key representing the
     * activated item position. Only used on tablets.
     */
    private static final String STATE_ACTIVATED_POSITION = "activated_position";

    /**
     * The fragment's current callback object, which is notified of list item
     * clicks.
     */
    private Callbacks mCallbacks = sDummyCallbacks;

    /**
     * The current activated item position. Only used on tablets.
     */
    private int mActivatedPosition = ListView.INVALID_POSITION;

    /**
     * A callback interface that all activities containing this fragment must
     * implement. This mechanism allows activities to be notified of item
     * selections.
     */
    public interface Callbacks {
        /**
         * Callback for when an item has been selected.
         */
        public void onItemSelected(int position);
    }

    /**
     * A dummy implementation of the {@link Callbacks} interface that does
     * nothing. Used only when this fragment is not attached to an activity.
     */
    private static Callbacks sDummyCallbacks = new Callbacks() {
        @Override
        public void onItemSelected(int id) {
        }
    };


    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public CheatListFragment() {
    }

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

        cheatListActivity = (CheatListActivity) getActivity();
        fontRoboto = Tools.getFontRobotoRegular(getActivity().getAssets());

        settings = cheatListActivity.getSharedPreferences(Konstanten.PREFERENCES_FILE, 0);

        gameObj = new Gson().fromJson(settings.getString(Konstanten.PREFERENCES_TEMP_GAME_OBJECT_VIEW, null), Game.class);

        if( gameObj == null) {
            new GetCheatsTask().execute(new Game());            
        } else {
            new GetCheatsTask().execute(gameObj);           
        }
    }

    private class GetCheatsTask extends AsyncTask<Game, Void, Void> {

        @Override
        protected Void doInBackground(Game... params) {

            if (params[0].getCheats() == null) {
                cheats = cheatListActivity.getCheatsForFragment(cheatListActivity.getIntent()); 
            } else {
                cheats = params[0].getCheats();
            }

            for (int i = 0; i < cheats.length; i++) {
                Log.d("Cheat Item ", cheats[i].getCheatTitle());
                cheatsArrayList.add(cheats[i]);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);

            cheatAdapter = new CheatAdapter(getActivity(), R.layout.cheatlist_item, cheatsArrayList);
            setListAdapter(cheatAdapter);
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Restore the previously serialized activated item position.
        if (savedInstanceState != null && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
            setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Activities containing this fragment must implement its callbacks.
        if (!(activity instanceof Callbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        mCallbacks = (Callbacks) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();

        // Reset the active callbacks interface to the dummy implementation.
        mCallbacks = sDummyCallbacks;
    }

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

        // Notify the active callbacks interface (the activity, if the
        // fragment is attached to one) that an item has been selected.
        // mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
        mCallbacks.onItemSelected(position);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mActivatedPosition != ListView.INVALID_POSITION) {
            // Serialize and persist the activated item position.
            outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
        }
    }

    /**
     * Turns on activate-on-click mode. When this mode is on, list items will be
     * given the 'activated' state when touched.
     */
    public void setActivateOnItemClick(boolean activateOnItemClick) {
        // When setting CHOICE_MODE_SINGLE, ListView will automatically
        // give items the 'activated' state when touched.
        getListView().setChoiceMode(activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE);
    }

    private void setActivatedPosition(int position) {
        if (position == ListView.INVALID_POSITION) {
            getListView().setItemChecked(mActivatedPosition, false);
        } else {
            getListView().setItemChecked(position, true);
        }

        mActivatedPosition = position;
    }

    private class CheatAdapter extends ArrayAdapter<Cheat> {

        private final ArrayList<Cheat> items;

        public CheatAdapter(Context context, int textViewResourceId, ArrayList<Cheat> items) {
            super(context, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.cheatlist_item, null);
            }

            try {
                Cheat cheat = items.get(position);
                if (cheat != null) {

                    TextView tt = (TextView) v.findViewById(R.id.game_title);
                    tt.setText(cheat.getCheatTitle());
                    tt.setTypeface(fontRoboto);

                    // Durchschnittsrating (nicht Member-Rating)
                    RatingBar ratingBar = (RatingBar) v.findViewById(R.id.small_ratingbar);
                    ratingBar.setNumStars(5);
                    ratingBar.setRating(cheat.getRatingAverage() / 2);

                    ImageView flag_newaddition = (ImageView) v.findViewById(R.id.newaddition);
                    if (cheat.getDayAge() < Konstanten.CHEAT_DAY_AGE_SHOW_NEWADDITION_ICON) {
                        flag_newaddition.setImageResource(R.drawable.flag_new);
                        flag_newaddition.setVisibility(View.VISIBLE);
                    } else {
                        flag_newaddition.setVisibility(View.GONE);
                    }

                    ImageView flag_screenshots = (ImageView) v.findViewById(R.id.screenshots);
                    if (cheat.isScreenshots()) {
                        flag_screenshots.setVisibility(View.VISIBLE);
                        flag_screenshots.setImageResource(R.drawable.flag_img);
                    } else {
                        flag_screenshots.setVisibility(View.GONE);
                    }

                    ImageView flag_german = (ImageView) v.findViewById(R.id.flag);
                    if (cheat.getLanguageId() == 2) { // 2 = Deutsch
                        flag_german.setVisibility(View.VISIBLE);
                        flag_german.setImageResource(R.drawable.flag_german);
                    } else {
                        flag_german.setVisibility(View.GONE);
                    }

                }
            } catch (Exception e) {
                Log.e(getClass().getName() + ".getView ERROR:", e.getMessage());
            }
            return v;
        }
    }
}

Detail view in two-pane mode (on a tablet):

public class CheatDetailTabletFragment extends Fragment implements OnClickListener {
    /**
     * The fragment argument representing the item ID that this fragment
     * represents.
     */
    public static final String ARG_ITEM_ID = "item_id";

    /**
     * The dummy content this fragment is presenting.
     */
    private CheatContent.CheatItem mItem;
    private View rootView;

    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public CheatDetailTabletFragment() {
    }

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

        ca = (CheatListActivity) getActivity();

        cheatTitleTypeface = Tools.getFontRobotoThin(getActivity().getAssets());
        cheatTextTypeface = Tools.getFontRobotoRegular(getActivity().getAssets());

        settings = getActivity().getSharedPreferences(Konstanten.PREFERENCES_FILE, 0);
        editor = settings.edit();

        if (getArguments().containsKey(ARG_ITEM_ID)) {
            // Load the dummy content specified by the fragment
            // arguments. In a real-world scenario, use a Loader
            // to load content from a content provider.

            mItem = CheatContent.ITEM_MAP.get(getArguments().getInt(ARG_ITEM_ID));
            cheatObj = new Gson().fromJson(getArguments().getString("cheatObj"), Cheat.class);

            cheatForumFragment = new Gson().fromJson(getArguments().getString("cheatForumFragment"), CheatForumFragment.class);
            cheatDetailMetaFragment = new Gson().fromJson(getArguments().getString("cheatDetailMetaFragment"), CheatDetailMetaFragment.class);
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.fragment_cheat_detail, container, false);

        // ...

        // Show the dummy content as text in a TextView.
        if (mItem != null) {
            ((TextView) rootView.findViewById(R.id.text_cheat_before_table)).setText(mItem.getCheatTitle());
            ((TextView) rootView.findViewById(R.id.text_cheat_title)).setText(mItem.getCheatId());
        }

        // ...

        populateView();         

        return rootView;
    }

    /**
     * Create Layout
     */
    private void populateView() {
        // fills the view. no problems here.
    }

    /**
     * Populate Table Layout
     */
    private void fillTableContent() {
        // filling table content here. no problems here.
    }

    private void fillSimpleContent() {
        // filling with other content. works fine, too.
    }

}

Detail view in one-pane mode (on a phone):

public class CheatViewPageIndicator extends FragmentActivity implements ReportCheatDialogListener, RateCheatDialogListener {

    // define variables...

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LayoutInflater inflater = LayoutInflater.from(this);
        intent = getIntent();

        viewLayout = inflater.inflate(intent.getIntExtra("layoutResourceId", R.layout.activity_cheatview_pager), null);
        setContentView(viewLayout);

        settings = getSharedPreferences(Konstanten.PREFERENCES_FILE, 0);
        editor = settings.edit();

        getActionBar().setHomeButtonEnabled(true);
        getActionBar().setDisplayHomeAsUpEnabled(true);

        try {
            gameObj = new Gson().fromJson(settings.getString(Konstanten.PREFERENCES_TEMP_GAME_OBJECT_VIEW, null), Game.class);
            if (gameObj == null) {
                gameObj = new Gson().fromJson(intent.getStringExtra("gameObj"), Game.class);
            }
            editor.putString(Konstanten.PREFERENCES_TEMP_GAME_OBJECT_VIEW, new Gson().toJson(gameObj));
            editor.commit();

            pageSelected = intent.getIntExtra("selectedPage", 0);
            activePage = pageSelected;
            pageIndicatorColor = intent.getIntExtra("pageIndicatorColor", Konstanten.CYAN_DARK);
            cheatObj = gameObj.getCheats();
            visibleCheat = cheatObj[pageSelected];

            getActionBar().setTitle(gameObj.getGameName());
            getActionBar().setSubtitle(gameObj.getSystemName());

            initialisePaging();
        } catch (Exception e) {
            Log.e(CheatViewPageIndicator.class.getName(), e.getMessage() + "");
        }

    }

    private void initialisePaging() {
        // ...
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.handset_cheatview_menu, menu);

        // Search
        getMenuInflater().inflate(R.menu.search_menu, menu);

        // Associate searchable configuration with the SearchView
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));

        return super.onCreateOptionsMenu(menu);
    }



    @Override
    protected void onResume() {
        super.onResume();
        invalidateOptionsMenu();
    }

    public ConnectivityManager getConnectivityManager() {
        if (cm == null) {
            cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        }
        return cm;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            // This ID represents the Home or Up button. In the case of this
            // activity, the Up button is shown. Use NavUtils to allow users
            // to navigate up one level in the application structure. For
            // more details, see the Navigation pattern on Android Design:
            //
            // http://developer.android.com/design/patterns/navigation.html#up-vs-back

            // onBackPressed();
            // return true;

            Intent upIntent = new Intent(this, CheatListActivity.class);
            NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // This activity is NOT part of this app's task, so create a new
                // task when navigating up, with a synthesized back stack.
                TaskStackBuilder.create(this)
                // Add all of this activity's parents to the back stack
                        .addNextIntentWithParentStack(upIntent)
                        // Navigate up to the closest parent
                        .startActivities();
            } else {
                // This activity is part of this app's task, so simply
                // navigate up to the logical parent activity.
                // NavUtils.navigateUpTo(this, upIntent);
                upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(upIntent);
                finish();
            }
            return true;
        }
        return super.onOptionsItemSelected(item);

    }       

}   

So in one pane mode (on a phone) when I click the "up" button in the action bar from CheatViewPageIndicator.java to get one up to CheatListActivity.java I get a nullpointer exception pointing to this line:

gameObj = new Gson().fromJson(intent.getStringExtra("gameObj"), Game.class);    

Seems like the "intent" is null when going back. I'm wondering why that is? What do I need to do to keep the data in the intent? (or any other solution how to get rid of the nullpointer is perfectly fine with me, too). I'm a bit desperate, I've been searching for a solution for too long.

Thanks a lot for the help.

Was it helpful?

Solution 2

I'm almost sure the problem comes from onOptionsItemSelected() in CheatViewPageIndicator. You don't need to start a new activity there, in master-detail pattern there's always one and the same activity at a time. Only fragments are changing.

To provide an ability to properly use "up" button you should just call fragmentTransaction.addToBackStack(null) while adding fragments. Android will handle all stack logic by itself. Don't do anything at all in android.R.id.home case, you don't need it.

OTHER TIPS

I generally use another way of specifying parent acivities, and it includes an attribute in the manifest file.

In the activity tag of CheatViewPageIndicator.java, add this attribute:

 <activity
        ...
        android:parentActivityName="your.app.package.CheatListActivity" >
 </activity>   

and comment out all the lines you wrote for upIntent.This should do the trick.

I ran into this problem and solved it by putting the intent extras into the upIntent right before Nav so that they would get passed up to the parent List activity.

public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            ...
            Intent upIntent = new Intent(this, CheatListActivity.class);
-->         upIntent.putExtras(getIntent().getExtras());
            NavUtils.getParentActivityIntent(this);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top