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:
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);
}
}
}
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()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With