Search code examples
androidandroid-fragmentsnavigationandroid-listfragment

Converting FragmentActivity to a ListFragment with listview -- null pointer exception


I am having issues converting my previously working FragmentActivity and Customlistadapter to a ListFragment to be used with my navigation drawer. Currently, the navigation drawer is working correctly.

Below is the modified code for GetContacts

public class GetContacts extends android.support.v4.app.ListFragment {

// Log tag
private static final String TAG = MainActivity.class.getSimpleName();

// json URL
private static final String url = "http://url.json.php";
private ProgressDialog pDialog;
private List<Contacts> contactList = new ArrayList<Contacts>();
private ListView listView;
private ContactListAdapter adapter;


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

    View rootview = inflater.inflate(R.layout.fragment_contacts, container, false);
    return rootview;

}


@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    //See the second issue about wotking with ListFragment's list.
    adapter = new ContactListAdapter(this, contactList);
    setListAdapter(adapter);

    // pDialog = new ProgressDialog(this);
    // Showing progress dialog before making http request
    // pDialog.setMessage("Loading...");
    //pDialog.show();


    // Creating volley request obj
    JsonArrayRequest contactReq = new JsonArrayRequest(url,
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    Log.d(TAG, response.toString());
                    hidePDialog();

                    // Parsing json
                    for (int i = 0; i < response.length(); i++) {
                        try {

                            JSONObject obj = response.getJSONObject(i);
                            Contacts contacts = new Contacts();
                            contacts.setDivision(obj.getString("Division"));
                            contacts.setName(obj.getString("name"));
                            contacts.setNumber(obj.getString("number"));


                            // adding contacts to contacts array
                            contactList.add(contacts);

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }

                    }

                    // notifying list adapter about data changes
                    // so that it renders the list view with updated data
                    adapter.notifyDataSetChanged();
                }
            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            VolleyLog.d(TAG, "Error: " + error.getMessage());
            hidePDialog();

        }
    });

    // Adding request to request queue
    AppController.getInstance().addToRequestQueue(contactReq);
    //end volley code paste
}


public void onDestroy() {
    super.onDestroy();
    hidePDialog();
}

private void hidePDialog() {
    if (pDialog != null) {
        pDialog.dismiss();
        pDialog = null;
    }
}

}

Additionally, I have modified the code for ContactListAdapter. Currently I am getting the following compile error. "Error:(49, 56) error: cannot find symbol method getContext()". I checked this SO question and tried to understand what is causing this issue with no luck.

public class ContactListAdapter extends BaseAdapter {
private Activity activity;
private Fragment fragment;
private LayoutInflater inflater;
private List<Contacts> contactItems;


public ContactListAdapter(Fragment fragment, List<Contacts> contactItems) {
    this.fragment = fragment;
    this.contactItems = contactItems;
}

@Override
public int getCount() {
    return contactItems.size();
}

@Override
public Object getItem(int location) {
    return contactItems.get(location);
}

@Override
public long getItemId(int position) {
    return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {


    if (convertView == null){
        LayoutInflater inflater = (LayoutInflater)    getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.list_rowcontacts, null);
    }

    TextView division = (TextView) convertView.findViewById(R.id.division);
    TextView name = (TextView) convertView.findViewById(R.id.name);
    TextView number = (TextView) convertView.findViewById(R.id.number);


    // getting contact data for the row
    Contacts m = contactItems.get(position);


    // Division
    division.setText(m.getDivision());

    // Name
    name.setText("Name: " + m.getName());

    // number
    number.setText("Number: " + m.getNumber());


    return convertView;
}

}


Solution

  • The main problem is that a FragmentActivity isn't a Fragment, is an Activity (see the reference here). You can't instantiate it as a Fragment like you do in selectItem().

    If you want contacts to be loaded in your main activity (the one with the navigation drawer) then GetContacts should inherit from Fragment instead of FragmentActivity.

    UPDATE

    There are three issues with your code.

    Null pointer exception (setting the adapter in the right place)

    First you should move everything related to setting up the adapter to a method that is called after the Fragment is attached to its Activity (more info about the Fragment's lifecycle here). In onCreateView the Activity hasn't attached the Fragment yet so the Context of the adapter won't be available (see the third issue). Override onAttach() and place it there:

    public class GetContacts extends ListFragment {
    
    // Log tag
    private static final String TAG = MainActivity.class.getSimpleName();
    
    // json URL
    private static final String url = "http://url.json.php";
    private ProgressDialog pDialog;
    private List<Contacts> contactList = new ArrayList<Contacts>();
    private ListView listView;
    public ContactListAdapter adapter;
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_contacts, container, false);
        return rootView;
    }
    
    @Override
    public void onAttach(Activity activity) {
        //See the third issue about the Context passed to the adapter.
        adapter = new ContactListAdapter(activity, contactList);
        //See the second issue about working with ListFragment's list.
        setListAdapter(adapter);
    
       // pDialog = new ProgressDialog(this);
    
        // Showing progress dialog before making http request
      // pDialog.setMessage("Loading...");
       //pDialog.show();
    
    
        // Creating volley request obj
        JsonArrayRequest contactReq = new JsonArrayRequest(url,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        Log.d(TAG, response.toString());
                        hidePDialog();
    
                        // Parsing json
                        for (int i = 0; i < response.length(); i++) {
                            try {
    
                                JSONObject obj = response.getJSONObject(i);
                                Contacts contacts = new Contacts();
                                contacts.setDivision(obj.getString("Division"));
                                contacts.setName(obj.getString("name"));
                                contacts.setNumber(obj.getString("number"));
    
    
                                // adding contacts to contacts array
                                contactList.add(contacts);
    
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
    
                        }
    
                        // notifying list adapter about data changes
                        // so that it renders the list view with updated data
                        adapter.notifyDataSetChanged();
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                VolleyLog.d(TAG, "Error: " + error.getMessage());
                hidePDialog();
    
            }
        });
    
        // Adding request to request queue
        AppController.getInstance().addToRequestQueue(mcontactReq);
        //end volley code paste
    }
    
    
    public void onDestroy() {
        super.onDestroy();
        hidePDialog();
    }
    
    private void hidePDialog() {
        if (pDialog != null) {
            pDialog.dismiss();
            pDialog = null;
        }
    }
    }
    

    Null pointer exception (working with ListFragment's list

    ListFragments provide some methods like setListAdapter() to work with the inner ListView without having to get it first with findViewById(), but they work with their "default" ListView. For identifying this ListView they look in the layout for a ListView with the id @id/android:list. You're using a custom id for your ListView so setListAdapter() cannot find the default ListView (more info about ListFragment here). You have three posible solutions:

    First: If your layout doesn't contain more than a ListView you don't have to use a custom layout. Don't override onCreateView, removing everything about inflating a custom view.

    Second: Change the id of your ListView to @id/android:list in the xml layout file.

    Third: Maintain your custom layout and id, but always use the ListView getting it first with findViewById(), not with the ListFragment's methods for dealing with lists (like when GetContacts was a FragmentActivity). So this:

    listView  = (ListView) rootView.findViewById(android.R.id.list);
    adapter = new ContactListAdapter(this, contactList);
    setListAdapter(adapter);
    

    would become this:

    listView  = (ListView) getView().findViewById(android.R.id.list);
    adapter = new ContactListAdapter(this, contactList);
    listView.setListAdapter(adapter);
    

    The first two allow you to work with the default list. I don't recomend you to go for the third, since ListFragments are designed for helping you in dealing with lists faster and you wouldn't be using their helper methods, and therefore using a ListFragment like any other Fragment.

    P.S. The code for GetContacts I posted before use the second solution.

    LayoutInflater in Adapter

    With BaseAdapter you have to keep a reference of the Context in wich the adapter will work. Since Fragment isn't a Context, fragments have to use the Activity they're attached to as the Context. Look a this line in the GetContact's code I just updated (in GetContact's onAttached()):

    adapter = new ContactListAdapter(activity, contactList);
    

    Then in the adapter you use that as the Context to get the LayoutInflater:

    public class ContactListAdapter extends BaseAdapter {
    //There is no need for this two
    //private Activity activity;
    //private Fragment fragment;
    
    //We use this instead
    private Context context
    
    private LayoutInflater inflater;
    private List<Contacts> contactItems;
    
    //Now the constructor receives a Context
    public ContactListAdapter(Context context, List<Contacts> contactItems) {
        this.context = context;
        this.contactItems = contactItems;
    }
    
    //Every other method remains the same
    ...
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //Now we use context to get the LayoutInflater
        if (convertView == null){
            LayoutInflater inflater = (LayoutInflater)    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.list_rowcontacts, null);
        }
    
        //The rest of the code is the same
        ...
    }
    }
    

    EDIT

    Added the changed code for the "set up the adapter in the right place".

    Changed the third issue for using a BaseAdapter (I got confused).