NoBugsOnlyFeatures
NoBugsOnlyFeatures

Reputation: 179

Why does onAttach not recognize the correct instanceof context?

I am having troubles getting Android to set my listener. Somehow the context isn't of the type I am expecting it to be. I'm not sure where I am going wrong.

Below is AddEditCharacterFragment.java, where it is throwing an exception because context isn't of the type I expect.

public class AddEditCharacterFragment extends Fragment {

    public static final String ARG_PARAM1 = "param1";

    private InitiativeTrackerDBHelper mHelper;

    private String mParam1;

    private Character mCharacter;

    public AddEditCharacterFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment AddEditCharacterFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static AddEditCharacterFragment newInstance() {
        AddEditCharacterFragment fragment = new AddEditCharacterFragment();
        Bundle args = new Bundle();
        //args.putInt(ARG_PARAM1, id);
        fragment.setArguments(args);
        return fragment;
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View v = inflater.inflate(R.layout.fragment_add_character, container, false);

        mHelper = new InitiativeTrackerDBHelper(getActivity());

        mCharacter = mHelper.addCharacter();

        EditText characterNameEditText = (EditText) v.findViewById(R.id.character_name_text_edit);
        characterNameEditText.setText(mCharacter.getName());
        characterNameEditText.addTextChangedListener(new TextWatcher() {
            public void onTextChanged(CharSequence c, int start, int before, int count) {
                mCharacter.setName(c.toString());
            }

            public void beforeTextChanged(CharSequence c, int start, int before, int after) {
            }

            public void afterTextChanged(Editable c) {
            }
        });

        EditText modifierPicker =
                (EditText) v.findViewById(R.id.modEditText);

        modifierPicker.setText(Integer.toString(mCharacter.getModifier()));

        modifierPicker.addTextChangedListener(new TextWatcher() {
            public void onTextChanged(CharSequence c, int start, int before, int count) {
                mCharacter.setModifier(Integer.parseInt(c.toString()));
            }

            public void beforeTextChanged(CharSequence c, int start, int before, int after) {
            }

            public void afterTextChanged(Editable c) {
            }
        });

        Button saveButton = (Button) v.findViewById(R.id.saveButton);
        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mHelper != null)
                {
                    mHelper.updateCharacter(mCharacter);
                    Toast.makeText(getActivity(), "Update complete!", Toast.LENGTH_LONG).show();
                    mListener.onCharacterSave();
                }

            }
        });

        return v;
    }

    private OnCharacterSave mListener;

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

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

    public interface OnCharacterSave {
        public void onCharacterSave();
    }
}

AddEditCharacterActivity is the activity for the fragment above.

public class AddEditCharacterActivity extends SingleFragmentActivity
        implements AddEditCharacterFragment.OnCharacterSave {

    @Override
    protected Fragment createFragment() {
        return AddEditCharacterFragment.newInstance();
    }

    @Override
    public void onCharacterSave() {
        FragmentManager fm = getFragmentManager();

        // Get the container for the character list
        InitiativeListFragment initiativeListFragment = (InitiativeListFragment)
                fm.findFragmentById(R.id.fragmentContainer);

        // Update the UI
        initiativeListFragment.updateInitiativeList();
    }
}

InitiativeTrackerActivity which is using an intent to start the AddEditCharacterActivity and subsequently AddEditCharacterFragment.

    public class InitiativeTrackerActivity extends SingleFragmentActivity
    implements InitiativeListFragment.OnCharacterListListener, AddEditCharacterFragment.OnCharacterSave {

    @Override
    protected Fragment createFragment() {
        return InitiativeListFragment.newInstance();
    }

    @Override
    public void onAddCharacter() {
        Intent intent = new Intent(this, AddEditCharacterActivity.class);
        startActivity(intent);
    }

    @Override
    public void onCharacterSave() {
        FragmentManager fm = getFragmentManager();

        // Get the container for the character list
        InitiativeListFragment initiativeListFragment = (InitiativeListFragment)
                fm.findFragmentById(R.id.fragmentContainer);

        // Update the UI
        initiativeListFragment.updateInitiativeList();
    }
}

And the base class of SingleFragmentActivity for reference:

    public abstract class SingleFragmentActivity extends AppCompatActivity {

    protected abstract Fragment createFragment();

    protected int getLayoutId() {
        return R.layout.activity_single_fragment;
    }

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

        FragmentManager fm = getFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);

        if (fragment == null) {
            fragment = createFragment();
            fm.beginTransaction()
                    .add(R.id.fragmentContainer, fragment)
                    .commit();
        }
    }
}

And InitiativeListFragment.java

package com.example.twistedpurpose.finalproject;

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link InitiativeListFragment.OnCharacterListListener} interface
 * to handle interaction events.
 * Use the {@link InitiativeListFragment#newInstance} factory method to
 * create an instance of this fragment.
 */
public class InitiativeListFragment extends Fragment {

    private InitiativeTrackerDBHelper.CharacterCursor mCursor;

    private CharacterCursorAdapter adapter;

    private OnCharacterListListener mListener;

    public InitiativeListFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment InitiativeListFragment.
     */
    public static InitiativeListFragment newInstance() {
        return new InitiativeListFragment();
    }

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View v = inflater.inflate(R.layout.fragment_initiative_list, container, false);

        //getActivity().deleteDatabase("characters.db");

        Context context = getActivity();

        // 1. Create a new InitiativeTrackerDBHelper
        InitiativeTrackerDBHelper dbHelper = new InitiativeTrackerDBHelper(context);


        // 2. Query the characters and obtain a cursor (store in mCursor).
        mCursor = dbHelper.queryCharacters();

        // Find ListView to populate
        ListView characterListView = (ListView) v.findViewById(R.id.character_listView);
        // Setup cursor adapter using cursor from last step
        adapter = new CharacterCursorAdapter(context, mCursor);
        // Attach cursor adapter to the ListView
        characterListView.setAdapter(adapter);

        Button rollButton = (Button) v.findViewById(R.id.rollBtn);
        rollButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                InitiativeTrackerDBHelper dbHelper = new InitiativeTrackerDBHelper(getContext());
                List<Character> characterList = dbHelper.getCharacters();

                InitiativeRoller.rollInitiative(characterList);

                for (Character c : characterList) {
                    dbHelper.updateCharacter(c);
                }

                updateInitiativeList();
                Toast.makeText(getContext(), "Roll initiative!", Toast.LENGTH_SHORT).show();
            }
        });

        Button addButton = (Button) v.findViewById(R.id.addBtn);
        addButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mListener != null) {
                    mListener.onAddCharacter();
                }
            }
        });

        return v;
    }

    public void updateInitiativeList(){
        if(mCursor != null && adapter != null){
            mCursor.requery();
            adapter.notifyDataSetChanged();
        }

    }

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

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

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     * <p>
     * See the Android Training lesson <a href=
     * "http://developer.android.com/training/basics/fragments/communicating.html"
     * >Communicating with Other Fragments</a> for more information.
     */
    public interface OnCharacterListListener {
        public void onAddCharacter();
    }

    /**
     * A character cursor adaptor for adding characters
     * to a list
     */
    private static class CharacterCursorAdapter extends CursorAdapter {

        private InitiativeTrackerDBHelper.CharacterCursor mCharacterCursor;

        public CharacterCursorAdapter(Context context, InitiativeTrackerDBHelper.CharacterCursor cursor) {
            super(context, cursor, 0);
            mCharacterCursor = cursor;
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            // Use a layout inflater to get a row view
            LayoutInflater inflater = (LayoutInflater) context
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            return inflater.inflate(R.layout.character_listview, parent, false);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            TextView characterName = (TextView) view.findViewById(R.id.name);
            TextView characterMod = (TextView) view.findViewById(R.id.mod);
            TextView characterInit = (TextView) view.findViewById(R.id.init);

            characterName.setText(mCharacterCursor.getCharacter().getName());
            characterMod.setText(Integer.toString(mCharacterCursor.getCharacter().getModifier()));
            characterInit.setText(Integer.toString(mCharacterCursor.getCharacter().getTotalInitiative()));
        }
    }
}

Upvotes: 2

Views: 1000

Answers (2)

Martin Marconcini
Martin Marconcini

Reputation: 27266

Your problem is that you do NOT implement the Interface in your activity…

public class AddEditCharacterActivity extends SingleFragmentActivity {

This does not implement OnCharacterSave.

This is what I meant with the earlier comment.

UPDATE:

You are misunderstanding Fragments.

// Get the container for the character list -> This is not true. You're not getting the "Container", you're trying to get an actual instance of a fragment.

    InitiativeListFragment initiativeListFragment = (InitiativeListFragment)
            fm.findFragmentById(R.id.fragmentContainer);

This would be fine, if that fragment were there.

Let me put it in a more graphic way, this is what you're doing… (give or take)

  1. Start Activity XXX (SingleFragmentActivity).
  2. At some point, InitiativeListFragment in R.id.fragmentContainer is replaced by AddEditCharacterActivity / AddEditCharacterFragment combo.
  3. At this point, R.id.fragmentContainer contains a fragment of type AddEditCharacterFragment.
  4. Since your Activity implements OnCharacterSave, so far so good.
  5. Still in the same activity/fragment combo, you call onCharacterSave() which is implemented (see #4), so all is good.
  6. You then tell the Fragment manager to get you the fragment in R.id.fragmentContainer and you explicitly say (aka: cast) that the Fragment is of the type InitiativeListFragment, but… your Activity should know this is not the case… because the current fragment is AddEditCharacterFragment.

What you ought to do is:

  1. Re-Read about FragmentManager and FragmentTransactions.
  2. If you're going to pass info to another Fragment that is not currently visible/started/attached/etc. then you have to obtain a reference (via TAG if you have one).
  3. Then maybe add it to a container if possible while passing the "data" via a Bundle.

It's completely unclear what you're trying to do and it what order because your code doesn't really have a lot of separation of concerns so as you can see your Activities and Fragments are becoming monolithic monsters full of code and business logic. There are solutions and alternatives (Read about Model-View-Presenter or similar patterns) that can ease the mess while providing an easier environment to test your code.

That being said, regardless of the complexity of your code, I believe you need to understand WHY you're getting the exception, and I have the feeling that you need to practice that a little bit.

In short… when you do findFragmentById, you do get the Fragment (if existing), but you can't just cast it to whatever you want.

OLD COMMENTS:

newInstance() static methods should generally live inside the Fragments and return new YourFragment();

What I mean is the Fragment creation is usually done via a static method IN the fragment.

Say you have

MyFragment extends Fragment {
    public static MyFragment newInstance() {
        return new MyFragment();
    }

    public MyFragment() {
          // empty constructor is most of the time needed to restore.
    }
}

Then from the activity you usually do what you're doing, but the fragment instance is created by calling MyFragment.newInstance(); (this is how Google does it).

I suggest you add your fragment by Tag as well (it's faster). So you do

final Fragment existing = getSupportFragmentManager().findFragmentByTag(tag);

if (existing == null) {
    final Fragment newInstance = MyFragment.newInstance();
       getSupportFragmentManager()
           .beginTransaction()
           .add(R.id.fragmentContainer, newInstance, tag)
           .commit();
 }

Tag is a String and you can keep it in constants (final static String MYFRAGMENT_TAG = "MYFRAGMENT_TAG"; for example).

Are you using Support.V4 fragments? If so you need to change getFragmentManager() to getSupportFragmentManager() (It looks like you are, because you have AppCompatActivity.

Also, the fragment transaction, should be surrounded by if (savedInstaceState == null) { // do it here }

Upvotes: 2

szholdiyarov
szholdiyarov

Reputation: 385

I do not see any problems with creating a fragment instance in your question as stated in other answers.

I think the problem is that your context is AddEditCharacterActivity where you do not implement OnCharacterSave interface.

So you should add:

public class AddEditCharacterActivity extends SingleFragmentActivity implements OnCharacterSave

Upvotes: 0

Related Questions