0

Super hard one to explain.

This is the error I get in my reporting:

Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference

This seems to happen intermittently when you go into and out of the fragment. The error seems to happen in the adaptor.

This is where it is called:

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        getActivity().setTitle("Shipments");

        myView = inflater.inflate(R.layout.shipments_out_layout, container, false);

        listView = myView.findViewById(R.id.listView);

        fetchShipments();

        return myView;
    }

    /**
     * Fetch shipments
     */
    public void fetchShipments()
    {
        shipmentsService.fetchFromServer(getActivity());
    }

    /**
     * Show shipments
     */
    public void showShipments(){
        RealmResults<Shipment> savedShipments = shipmentsService.all();

        ShipmentsAdaptor adaptor = new ShipmentsAdaptor(savedShipments, this.getContext());

        listView.setAdapter(adaptor);
    }

And this is where the error is in the adaptor:

public class ShipmentsAdaptor extends ArrayAdapter<Shipment> {

private RealmResults<Shipment> dataSet;
Context mContext;

// View lookup cache
private static class ViewHolder {
    TextView stockItemId;
    TextView technicianName;
    TextView shipmentDate;
}

public ShipmentsAdaptor(RealmResults<Shipment> data, Context context){
    super(context, R.layout.shipments_out_row_item, data);
    this.dataSet = data;
    this.mContext = context;
}

It's this line specifically: super(context, R.layout.shipments_out_row_item, data);

I thought it may be something to do with the way we are inserting the context into the adaptor and then changing the page before its finished but that proved inconclusive.

Paste bin with adaptor:Adaptor

enter image description here

Phantômaxx
  • 36,442
  • 21
  • 78
  • 108
Lewis Smith
  • 957
  • 1
  • 7
  • 29

4 Answers4

1

The Fragment#getContext() is nullable. This method returns null when your fragment is detached from activity. The app crashes because you create the adapter while the fragment is not attached which results into a null passed to the constructor.

The method showShipments should only be called when the fragment is attached to the activity. There are callbacks onAttach() and onDetach() that will help you to detect the state. Also isAdded() returns you a boolean saying if the fragment is attached or not. Choose what is convenient for you.

Good luck!

Gennadii Saprykin
  • 4,305
  • 8
  • 28
  • 39
  • Thanks, makes a lot of sense, will be using the above code to see if that works – Lewis Smith Jul 13 '18 at 14:13
  • onAttach doesn't seem to be a method that can overrided in the fragment, am I doing this in the wrong place – Lewis Smith Jul 13 '18 at 14:19
  • https://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity) – Gennadii Saprykin Jul 13 '18 at 14:21
  • This doesn't change my answer in any way :) But thanks for adding more info. The solution is simple - just make sure the context that you pass to adapter is never null. – Gennadii Saprykin Jul 13 '18 at 14:37
  • Can I access the context directly from the adaptor, to stop having to pass it through – Lewis Smith Jul 13 '18 at 14:45
  • No. Adapter is a simple class that does mapping between the data and views. And it needs context to inflate those views, otherwise it won't be able to render anything. You need to get the activity context and pass it to the adapter. The activity context can be accessed by your fragment once it is attached. – Gennadii Saprykin Jul 13 '18 at 14:47
0

Try refactor your adapter using BaseAdapter as follow

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ShipmentsAdaptor extends BaseAdapter {

    private RealmResults<Shipment> dataSet;
    private Context mContext;

    // View lookup cache
    private static class ViewHolder {
        TextView stockItemId;
        TextView technicianName;
        TextView shipmentDate;
    }

    public ShipmentsAdaptor(RealmResults<Shipment> dataSet, Context context) {
        this.dataSet = dataSet;
        this.mContext = context;
    }

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

    @Override
    public Object getItem(int position) {
        return dataSet.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // Get the data item for this position
        Shipment shipment = (Shipment) getItem(position);
        // Check if an existing view is being reused, otherwise inflate the view
        ViewHolder viewHolder; // view lookup cache stored in tag

        final View result;

        if (convertView == null) {

            viewHolder = new ViewHolder();
            LayoutInflater inflater = LayoutInflater.from(mContext);
            convertView = inflater.inflate(R.layout.shipments_out_row_item, parent, false);
            viewHolder.stockItemId = convertView.findViewById(R.id.stockItemId);
            viewHolder.technicianName = convertView.findViewById(R.id.technicianName);
            viewHolder.shipmentDate = convertView.findViewById(R.id.shipmentDate);

            result = convertView;

            convertView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) convertView.getTag();
            result=convertView;
        }

        lastPosition = position; //use getItemId() instead

        if(shipment != null){
            viewHolder.stockItemId.setText(String.valueOf(shipment.id));
            if(shipment.technician != null){
                viewHolder.technicianName.setText(shipment.technician.name);
            }
            viewHolder.shipmentDate.setText(shipment.shippingDate);
        }

        // Return the completed view to render on screen
        return convertView;
    }
}
Abner Escócio
  • 2,133
  • 2
  • 11
  • 29
0

You can check for null during your adapter setup to avoid this. In a Fragment, getActivity can sometimes return null at different points during the Fragment lifecycle. For example, in showShipments

Activity a = getActivity();
if( a == null || a.isFinishing() ) {
    // Not in a valid state to show things anyway, so just stop and exit
    return;
}
ShipmentsAdaptor adaptor = new ShipmentsAdaptor(savedShipments, a);

You can also check isAdded(), and if that is false you can get null from getActivity().

Also, consider moving the call to fetchShipments() from onCreateView to onActivityCreated instead.

Tyler V
  • 3,246
  • 2
  • 13
  • 34
0

Looks like you are calling fetchShipments(); before the fragment layout view (myView) is returned hence it is null when the adaptor is instantiated.

Try:

Move fetchShipments(); from onCreateView() and place it in onResume() or override onStart() and call it from there

TennyApps
  • 141
  • 8