Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException when switching between fragments containing RecyclerView

I'm having a real hard time trying to figure this out. I'm getting a NullPointerException when switching between Fragments with RecyclerView but only in certain order. Code and Exception is below. First an explanation:

I have a fragment with this tabbed layout as seen here: enter image description here

3 of these tabs use the same Fragment containing a RecyclerView: Schedule, History and Test. Any of them will work if I select them first. Now the problem is when I go to one of the other aforementioned tabs to the right of the first one I selected! If I go to the left of the first one I selected, though, that will load just fine.

If I select Test then go to History and then to Schedule everything will work fine

If I select Test then Schedule those two will work, but then going to History will cause the exception.

If I select Schedule then History the exception will happen.

If I select History then Schedule those two will work, but then going to Test the exception will happen

If I select History then Test the exception will happen

I extended the RecycleView and put a break in the onMeasure to see if I could figure out what was going on and I noticed that the mAdapter of the RecyclerView was null. I checked inside the setAdapter() of the RecyclerView and it was always passed a valid adapter. So it seems like the mAdapter is getting cleared at some point. I just can't figure out why it only happens when I open the tabs going from left to right and not right to left...

Any help would be appreciated as I am reaching my wits end with this one!

Here is the NullPointerException:

Link to Exception trace (Had to put it here to avoid character limit)

EDIT: After catching the NullPointerException an IllegalStateException pops up too. Looks to be from the LayoutManager. Here is a link to that output

Here is the Fragment for tabbed view:

public class FKitViewer extends Fragment implements OnTabChangeListener {

    public static final String TAB_INFO = "Info";
    public static final String TAB_SCHEDULE = "Schedule";
    public static final String TAB_HISTORY = "History";
    public static final String TAB_TEST = "Test";
    public static final String TAB_REQS = "Reqs";

    private View mRoot;
    private TabHost mTabHost;
    private int mCurrentTab;

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

    @Override
    public View onCreateView(LayoutInflater li, ViewGroup container,
                                Bundle savedInstanceState){
        mRoot = li.inflate(R.layout.kit_view, container);
        mTabHost = (TabHost) mRoot.findViewById(android.R.id.tabhost);
        setupTabs();
        return mRoot;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState){
        super.onActivityCreated(savedInstanceState);
        setRetainInstance(true);
        mTabHost.setOnTabChangedListener(this);
        mTabHost.setCurrentTab(mCurrentTab);
        updateTab(TAB_INFO, R.id.tab1);
    }

    private void setupTabs(){
        mTabHost.setup();
        mTabHost.addTab(newTab(TAB_INFO, "Info", R.id.tab1));
        mTabHost.addTab(newTab(TAB_SCHEDULE, "Schedule", R.id.tab2));
        mTabHost.addTab(newTab(TAB_HISTORY, "History", R.id.tab3));
        mTabHost.addTab(newTab(TAB_REQS, "Reqs", R.id.tab4));
        mTabHost.addTab(newTab(TAB_TEST, "Test", R.id.tab5));
    }

    private TabSpec newTab(String tag, String label, int tabContentId){
        TabSpec tabSpec = mTabHost.newTabSpec(tag);
        tabSpec.setIndicator(label);
        tabSpec.setContent(tabContentId);
        return tabSpec;
    }

    private void updateTab(String tabId, int placeholder){
        FragmentManager fm = getFragmentManager();
        if(fm.findFragmentByTag(tabId) == null){
            if(tabId.equals(TAB_INFO))
                fm.beginTransaction()
                    .replace(placeholder, new FListiesView(), tabId)
                    .commit();
            if(tabId.equals(TAB_SCHEDULE))
                fm.beginTransaction()
                    .replace(placeholder, new FKitSchedule(false), tabId)
                    .commit();
            if(tabId.equals(TAB_HISTORY))
                fm.beginTransaction()
                    .replace(placeholder, new FKitSchedule(true), tabId)
                    .commit();
            if(tabId.equals(TAB_REQS))
                fm.beginTransaction()
                    .replace(placeholder, new KitRequirements(), tabId)
                    .commit();
            if(tabId.equals(TAB_TEST))
                fm.beginTransaction()
                    .replace(placeholder, new FKitSchedule(false), tabId)
                    .commit();
        }
    }

    @Override
    public void onTabChanged(String tabId){
        Log.d("MNB", "Changing to tab: " + tabId);
        if(TAB_INFO.equals(tabId)){
            updateTab(tabId, R.id.tab1);
            mCurrentTab = 0;
            return;
        }
        if(TAB_SCHEDULE.equals(tabId)){
            getActivity().getIntent().getExtras().putBoolean("com.crummy.history", false);
            updateTab(tabId, R.id.tab2);
            mCurrentTab = 1;
            return;
        }
        if(TAB_HISTORY.equals(tabId)){
            getActivity().getIntent().getExtras().putBoolean("com.crummy.history", true);
            updateTab(tabId, R.id.tab3);
            mCurrentTab = 2;
            return;
        }
        if(TAB_REQS.equals(tabId)){
            updateTab(tabId, R.id.tab4);
            mCurrentTab = 3;
            return;
        }
        if(TAB_TEST.equals(tabId)){
            getActivity().getIntent().getExtras().putBoolean("com.crummy.history", true);
            updateTab(tabId, R.id.tab5);
            mCurrentTab = 4;
            return;
        }
    }
}

Here is the fragment:

public class RVFKitSchedule extends Fragment 

implements LoaderManager.LoaderCallbacks{

private String mGetURL = "/index.php/droid/kitreport/";
private String mHistoryURL = "/index.php/droid/kithistory/";
private String mUpdateStatusURL = "/index.php/order_status/update/";
private Boolean mIsHistory = false;

private LinearLayout mPbl;

private int mDialogStatusLevel = 0;
private int mDialogLineId = -1;

private List<ScheduleRowModel> mItems = null;
private FKVRecyclerView mRecyclerView;
private KitScheduleRVAdapter mRVAdapter;
private int mItemCount;
private RecyclerView.LayoutManager mLayoutManager;

private FragmentActivity mParentActivity;

public RVFKitSchedule(){

}

public RVFKitSchedule(boolean isHistory){
    mIsHistory = isHistory;
    if(mIsHistory)
        mGetURL = mHistoryURL;
}

@Override
public View onCreateView(LayoutInflater li, ViewGroup vg, Bundle b){
    super.onCreateView(li, vg, b);
    return li.inflate(R.layout.rv_main, vg, false);
}


@Override
public void onActivityCreated(Bundle icicle){
    super.onActivityCreated(icicle);

    final InputMethodManager imm = (InputMethodManager) mParentActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
}

@Override
public void onViewCreated(View view, Bundle icicle){
    super.onViewCreated(view, icicle);
    mParentActivity = getActivity();
    mRecyclerView = (FKVRecyclerView)mParentActivity.findViewById(R.id.recyclerView);
    mLayoutManager = new LinearLayoutManager(mParentActivity);

    //mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    //mLayoutManager.
    mLayoutManager.scrollToPosition(0);
    mRecyclerView.setLayoutManager(mLayoutManager);

    RecyclerView.ItemDecoration itemDecoration = 
            new DividerItemDecoration(mParentActivity, DividerItemDecoration.VERTICAL_LIST);
    mRecyclerView.addItemDecoration(itemDecoration);
    mItems = new ArrayList<ScheduleRowModel>();
    mRVAdapter = new KitScheduleRVAdapter(mItems, mIsHistory);
    //mRecyclerView.setAdapter(null);
    if(mRecyclerView.getAdapter() != null)
        mRecyclerView.swapAdapter(mRVAdapter, true);
    else
        mRecyclerView.setAdapter(mRVAdapter);

    mRecyclerView.addOnItemTouchListener(
            new RecyclerItemClickListener(mParentActivity, new RecyclerItemClickListener.OnItemClickListener(){
                @Override
                public void onItemClick(View v, int position){
                    String status = ((TextView)v.findViewById(R.id.sr_status)).getText().toString();
                    if(status.equals("Open"))
                        mDialogStatusLevel = 0;
                    else if(status.equals("In Production"))
                        mDialogStatusLevel = 1;
                    else if(status.equals("Waiting"))
                        mDialogStatusLevel = 2;
                    else if(status.equals("Ready"))
                        mDialogStatusLevel = 3;
                    else if(status.equals("Shipped"))
                        mDialogStatusLevel = 4;
                    int lineId = Integer.parseInt(((TextView)v.findViewById(R.id.sr_order_line_id)).getText().toString());
                    mDialogLineId = lineId;
                    OrderStatusDialog df = new OrderStatusDialog(mParentActivity, mDialogStatusLevel){
                        @Override
                        protected void onDialogClick(DialogInterface di, final int which){
                            Runnable update = new Runnable(){
                                @Override
                                public void run(){
                                    updateStatus(mDialogLineId, which);
                                }
                            };
                            Thread t = new Thread(null, update, "MagentoBackground");
                            t.start();
                            try{
                                t.join();
                            }catch(InterruptedException e){
                                e.printStackTrace();
                            }
                            restartLoading();
                            dismiss();
                        }
                    };
                    df.show(getFragmentManager(), "dialog");
                }
            }){
                @Override
                public void onItemLongClick(View v, int pos){
                    String s = ((TextView)v.findViewById(R.id.sr_pn)).getText().toString();
                    String n = ((TextView)v.findViewById(R.id.sr_pn)).getText().toString();

                    CheckItemTypeTask task = new CheckItemTypeTask(s, null);
                    task.execute(mParentActivity);
                    int type = -1;
                    try{
                        type = task.get();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    if(type == ItemType.PART){
                        openPartViewer(s);
                    }else if(type == ItemType.KIT){
                        openKitViewer(s,n);
                    }else if(type == ItemType.ERROR){
                        Toast.makeText(mParentActivity, "Error getting item type!", Toast.LENGTH_LONG);
                    }else{
                        Toast.makeText(mParentActivity, "Invalid item type!", Toast.LENGTH_LONG).show();
                    }
                }
            }
    );

    if(mParentActivity.getIntent().getExtras() != null){
        Bundle b = mParentActivity.getIntent().getExtras();
        if(!b.isEmpty()){
            mGetURL += b.getString("com.crummy.kitNum");
        }
    }

    mPbl = (LinearLayout)mParentActivity.findViewById(R.id.main_pbl);
    mPbl.setVisibility(View.VISIBLE);


    LoaderManager lm = getLoaderManager();
    if(lm.getLoader(0) != null){
        lm.initLoader(0, null, this);
    }
    startLoading();
}

protected void startLoading(){
    getLoaderManager().initLoader(0, null, this);
}

protected void restartLoading(){
    getLoaderManager().restartLoader(0, null, this);
}

@Override
public Loader<Void> onCreateLoader(int arg0, Bundle arg1){
    Log.d("MNB", "FKitSchedule: onCreateLoader");
    AsyncTaskLoader<Void> loader = new AsyncTaskLoader<Void>(mParentActivity){
        @Override
        public Void loadInBackground(){
            try{
                getLines();
            }catch(Exception e){
                e.printStackTrace();
            }
            return null;
        }
    };
    loader.forceLoad();
    return loader;
}

@Override
public void onLoadFinished(Loader<Void> arg0, Void arg1){
    Log.d("MNB", "FKitSchedule: onLoadFinished");
    mPbl.setVisibility(View.GONE);
}

@Override
public void onLoaderReset(Loader<Void> arg0){

}

protected boolean openKitViewer(String kitNum, String kitName){
    boolean result = false;
    Intent i = new Intent(mParentActivity, GenFragmentActivity.class);
    i.putExtra("com.crummy.frag_layout_id", R.layout.kit_view_frag);
    i.putExtra("com.crummy.kitNum", kitNum);
    i.putExtra("com.crummy.kitName", kitName);
    startActivity(i);
    return result;
}

protected boolean openPartViewer(String itemNum){
    boolean result = false;
    Intent i = new Intent(mParentActivity, PartInfoFragmentActivity.class);
    i.putExtra("com.crummy.partnum", itemNum);
    startActivity(i);
    return result;
}

protected void getLines(){
    Log.d("MNB", "FKitSchedule: getLines() ");
    try{
        HttpEntity response = HttpHelper.tryHttp(mParentActivity, mGetURL, null);
        if(response == null)
            return;
        String res = EntityUtils.toString(response);
        if(res.equals("[]")){
            mParentActivity.runOnUiThread(noKitsError);
            return;
        }
        JSONArray jsona = new JSONArray(res);
        mItems = new ArrayList<ScheduleRowModel>();
        Log.d("MNB", "jsona.length() = " + jsona.length());
        for(int i=0; i < jsona.length(); i++){
            Log.d("MNB", "Reading json line " + i);
            JSONObject j = (JSONObject) jsona.get(i);
            ScheduleRowModel srm = new ScheduleRowModel();
            srm.custPo = j.getString("custPo");
            srm.dueDate = j.getString("dueDate");
            srm.partNum = j.getString("KitNumber");
            srm.qty = j.getInt("quantity");
            srm.status = j.getString("status");
            srm.lineId = j.getInt("id");
            srm.company = j.getString("name");
            srm.rev = j.getString("rev");
            if(!mIsHistory){
                if(!j.getString("note").equals("null"))
                    srm.note = j.getString("note");
            }
            mItems.add(srm);
        }
    }catch(Exception e){
        e.printStackTrace();
    }
    mParentActivity.runOnUiThread(returnRes);
}

private void addItemToList(ScheduleRowModel model){
    mItemCount++;       
    mRVAdapter.addData(model);
}

private HttpEntity updateStatus(int lineId, int statusId){
    HttpEntity resEntityPost = null;
    try{
        HttpClient client = new DefaultHttpClient();
        String host = HttpHelper.getHost(mParentActivity);
        HttpPost post = new HttpPost(host + mUpdateStatusURL);
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
        nameValuePairs.add(new BasicNameValuePair("line_id", Integer.toString(lineId)));
        nameValuePairs.add(new BasicNameValuePair("status", Integer.toString(statusId)));
        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
        HttpResponse res = client.execute(post);
        resEntityPost = res.getEntity();
    }catch(Exception e){
        e.printStackTrace();
    }
    return resEntityPost;
}
protected Runnable returnRes = new Runnable(){
    @Override
    public void run(){
        Log.d("MNB", "FKitSchedule returnRes ");
        mItemCount = 0;
        mRVAdapter.clear();
        if(mItems != null && mItems.size() > 0){
            for(int i = 0; i < mItems.size(); i++){
                addItemToList(mItems.get(i));
            }
        }
    }
};

private Runnable noKitsError = new Runnable()
{
    public void run(){
        Toast t = Toast.makeText(mParentActivity, "No orders for this kit.", Toast.LENGTH_SHORT);
        t.show();
    }
};

}

Here is the RecyclerView.Adapter:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class KitScheduleRVAdapter extends RecyclerView.Adapter<KitScheduleRVAdapter.ScheduleRowViewHolder>{

    private List<ScheduleRowModel> items;
    private boolean mIsHistory = false;

    KitScheduleRVAdapter(List<ScheduleRowModel> modelData, boolean isHistory){
        Log.d("MNB", "Constructing KitScheduleRVAdapter");
        if(modelData == null){
            throw new IllegalArgumentException(
                    "modelData must not be null");
        }
        this.items = modelData;
        mIsHistory = isHistory;
    }

    @Override
    public ScheduleRowViewHolder onCreateViewHolder(ViewGroup vg, int viewType){
        Log.d("MNB", "KitScheduleRVAdapter.onCreateViewHolder()");
        View itemView = LayoutInflater
                        .from(vg.getContext())
                        .inflate(R.layout.schedule_row, vg, false);
        return new ScheduleRowViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(ScheduleRowViewHolder vh, int pos){
        Log.d("MNB", "Binding Viewholder at pos "+pos);
        ScheduleRowModel model = items.get(pos);
        vh.date.setText(model.dueDate);
        vh.po.setText(model.custPo);
        vh.partnum.setText(model.partNum);
        vh.qty.setText(""+model.qty);
        vh.status.setText(model.status);
        vh.id.setText(""+model.lineId);
        vh.company.setText(model.company);
        vh.rev.setText(model.rev);
        vh.note.setText(model.note);

        Calendar cutoff = Calendar.getInstance();
        cutoff.add(Calendar.DAY_OF_MONTH, 14);
        Date co = cutoff.getTime();

        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
        Date d = new Date();
        try {
            d = s.parse(model.dueDate);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        View statusBar = vh.statusBar;
        if(!mIsHistory){
            if(model.status.equals("Ready")){
                statusBar.setBackgroundResource(R.color.sh_ready);
            }else if(model.status.equals("Waiting")){
                statusBar.setBackgroundResource(R.color.sh_waiting);
            }else if(model.status.equals("In Production")){
                statusBar.setBackgroundResource(R.color.sh_inprod);
            }
            else if(d.before(co)){
                statusBar.setBackgroundResource(R.color.sh_soon);
            }else{
                statusBar.setBackgroundResource(R.color.sh_open);
            }
        }
        else
        {
            statusBar.setBackgroundResource(R.color.sh_ready);
        }
    }

    @Override
    public int getItemCount(){
        return items.size();
    }

    public void addData(ScheduleRowModel data){
        items.add(data);
        this.notifyItemInserted(items.size() - 1);
    }

    public void clear(){
        Log.d("MNB", "KitScheduleRVAdapter.clear()");
        int itemsCleared = items.size();
        items.clear();
        notifyItemRangeRemoved(0, itemsCleared);
    }

    public final static class ScheduleRowViewHolder extends RecyclerView.ViewHolder{
        TextView date;
        TextView po;
        TextView qty;
        TextView partnum;
        TextView status;
        TextView id;
        TextView company;
        TextView rev;
        TextView note;
        View statusBar;

        public ScheduleRowViewHolder(View itemView){
            super(itemView);
            date = (TextView) itemView.findViewById(R.id.sr_date);
            po = (TextView) itemView.findViewById(R.id.sr_po);
            qty = (TextView) itemView.findViewById(R.id.sr_qty);
            partnum = (TextView) itemView.findViewById(R.id.sr_pn);
            status = (TextView) itemView.findViewById(R.id.sr_status);
            id = (TextView) itemView.findViewById(R.id.sr_order_line_id);
            company = (TextView) itemView.findViewById(R.id.sr_company);
            rev = (TextView) itemView.findViewById(R.id.sr_rev);
            note = (TextView) itemView.findViewById(R.id.sr_note);
            statusBar = itemView.findViewById(R.id.sr_status_bar);
        }
    }
}
like image 400
Matt Swanson Avatar asked Nov 06 '14 21:11

Matt Swanson


Video Answer


1 Answers

You should do your view assignments (and LayoutManager assignment) in onViewCreated() instead of onActivityCreated() -- onActivityCreated() isn't going to occur again when your Fragment gets detached and re-attached through the ViewPager. The view will be destroyed (onDestroyView()) and recreated (onCreateView()) as it goes away and returns, but the RecyclerView will never get its LayoutManager in that case.

EDIT: Pulled from the comments, another problem is using the parent Activity to resolve the RecyclerView instead of using the Fragment's View. Since findViewById() does a depth-first search, if you have multiple Fragments attached that contain views with the same ID, you may end up getting the wrong view, but everything will still compile and run, just with unexpected results. Use the view returned in onViewCreated() to restrict your view search to just the layout you inflated in onCreateView().

like image 76
Kevin Coppock Avatar answered Oct 08 '22 11:10

Kevin Coppock