Reputation: 20080
I have an Activity
, which itself has three Fragment
s.
In one of these fragments, there is a RecyclerView
with a custom adapter, and clicking on one of its items would go to another page, which is a new instance of the same Activity
. However, a certain behaviour causes an error in my app.
From my Activity, clicking on one of the items brings up the new instance of the same Activity, which is fine. Then I press the back button and I am taken back to the first Activity. But clicking on one of these items again (to launch a new instance of the same Activity) causes the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
It is also important to consider that I am calling the new instance of the Activity (i.e. where the three items are), in one of the fragments I have in my Activity. So, when I am calling it, I have something like:
public class MyActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
ViewPager viewPager = (ViewPager) findViewById(R.id.detail_viewpager);
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
TabLayout tabLayout = (TabLayout) findViewById(R.id.detail_tabs);
tabLayout.setTabTextColors(
ContextCompat.getColor(this, R.color.text_white_secondary),
ContextCompat.getColor(this, R.color.text_white));
tabLayout.setSelectedTabIndicatorColor(ContextCompat.getColor(this, R.color.white));
tabLayout.setupWithViewPager(viewPager);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
}
...
public class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return 3;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0: return new MainFragment();
case 1: return new MyFragment();
case 2: return new MyOtherFragment();
}
return null;
}
@Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.tab_main_frag).toUpperCase(l);
case 1:
return getString(R.string.tab_my_frag).toUpperCase(l);
case 2:
return getString(R.string.tab_my_other_frag).toUpperCase(l);
}
return null;
}
}
...
public static class MyFragment extends Fragment implements MyRVAdapter.OnEntryClickListener {
...
private ArrayList<ItemObj> mArrayList;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
doStuff();
...
}
private void doStuff() {
...
mArrayList = ...;
MyRVAdapter adapter = new MyRVAdapter(getActivity(), mArrayList);
adapter.setOnEntryClickListener(new MyRVAdapter.OnEntryClickListener() {
@Override
public void onEntryClick(View view, int position) {
Intent intent = new Intent(getActivity(), MyActivity.class);
intent.putExtra("INFORMATION", mArrayList.get(position));
startActivity(intent);
}
});
}
...
}
...
}
And here is part of my custom adapter:
public class MyRVAdapter extends RecyclerView.Adapter<MyRVAdapter.MyViewHolder> {
public static class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
...
MyViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
...
}
@Override
public void onClick(View v) {
// The user may not set a click listener for list items, in which case our listener
// will be null, so we need to check for this
if (mOnEntryClickListener != null) {
mOnEntryClickListener.onEntryClick(v, getLayoutPosition());
}
}
}
private Context mContext;
private ArrayList<ItemObj> mArray;
public MyRVAdapter(Context context, ArrayList<ItemObj> array) {
mContext = context;
mArray = array;
}
@Override
public int getItemCount() {
return mArray.size();
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.tile_simple, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ItemObj anItem = mArray.get(position);
...
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
private static OnEntryClickListener mOnEntryClickListener;
public interface OnEntryClickListener {
void onEntryClick(View view, int position);
}
public void setOnEntryClickListener(OnEntryClickListener onEntryClickListener) {
mOnEntryClickListener = onEntryClickListener;
}
}
Here is the error in full:
01-23 14:07:59.083 388-388/com.mycompany.myapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mycompany.myapp, PID: 388
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
at android.content.ComponentName.<init>(ComponentName.java:77)
at android.content.Intent.<init>(Intent.java:4570)
at com.mycompany.myapp.MyActivity$MyFragment$1.onEntryClick(MyActivity.java:783)
at com.mycompany.myapp.adapter.MyRVAdapter$MyViewHolder.onClick(MyRVAdapter.java:42)
at android.view.View.performClick(View.java:5197)
at android.view.View$PerformClick.run(View.java:20926)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5951)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1400)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1195)
The error points to the first line: Intent intent = new Intent(getActivity(), MyActivity.class);
(from the fragment) first, with line after (in the error) pointing to mOnEntryClickListener.onEntryClick(v, getLayoutPosition());
from the overriden onClick
method in the custom adapter.
I have also read similar answers, but they have not solved my issue.
Edit:
By using:
if (getActivity() == null) {
Log.d(LOG_TAG, "Activity context is null");
} else {
Intent intent = new Intent(getActivity(), MyActivity.class);
intent.putExtra("INFORMATION", mArrayList.get(position));
startActivity(intent);
}
in the inner class (onEntryClick
) in the fragment, I found that calling getActivity()
returns null
.
Upvotes: 6
Views: 41823
Reputation: 1
Even if the problem is already solved, I want to say what my problem is and my solution.
Problem
In my code I have added a listener with firebaseAuth.addAuthStateListener(listener)
, so when the user does the login and the new activity starts, the listener
is executed and causes the error (because inside I have another startActivity(getContext(), ...)
).
Solution
Use the method firebaseAuth.removeAuthStateListener(listener)
passing the listener's reference and that solves the problem. You have to remove the reference before starting the activity that causes the problem.
Upvotes: 0
Reputation: 3050
In your fragment create a variable of type Your Activity:
public MainActivity activity;
Then use the method on attach and assigns the vale:
@Override
public void onAttach(Activity activity){
this.activity = activity;
}
Then use your intent with the activity as a context:
Intent mIntent = new Intent(activity, MyActivity.class);
Upvotes: 0
Reputation: 5773
So, the issue is this line
private static OnEntryClickListener mOnEntryClickListener;
Since it is static you have a single instance of the class at runtime. When you click on an item, a second instance of the same Activity is created, and another instance of mOnEntryClickListener
is created too, overwriting the previous one. So, when you press back to return to the first instance of the Activity, you are using the instance of mOnEntryClickListener
of the second Activity, that has been destroyed.
Upvotes: 13
Reputation: 2600
In the oncreate of your fragment, hold reference of your activity and check if your activity is in resumed state before launching other activity.
public static class MyFragment extends Fragment implements MyRVAdapter.OnEntryClickListener {
...
private ArrayList<ItemObj> mArrayList;
private MyActivity mActivity;
@Override
public void onCreate(Bundle savedInstanceState) {
mActivity = getActivity();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
doStuff();
...
}
private void doStuff() {
...
mArrayList = ...;
MyRVAdapter adapter = new MyRVAdapter(getActivity(), mArrayList);
adapter.setOnEntryClickListener(new MyRVAdapter.OnEntryClickListener() {
@Override
public void onEntryClick(View view, int position) {
if (!mActivity.isFinishing() {
Intent intent = new Intent(mActivity, MyActivity.class);
intent.putExtra("INFORMATION", mArrayList.get(position));
startActivity(intent);
}
}
});
}
...
}
Upvotes: 0
Reputation: 3741
try to use my solution: in your MyActivity, create a new function:
public void openAnotherActivity(ObjectItem item) {
Intent intent = new Intent(MyApplication.getInstance(), MyActivity.class);
intent.putExtra("INFORMATION", item);
startActivity(intent);
}
Then modify doStuff() as :
private void doStuff() {
...
mArrayList = ...;
MyRVAdapter adapter = new MyRVAdapter(getActivity(), mArrayList);
adapter.setOnEntryClickListener(new MyRVAdapter.OnEntryClickListener() {
@Override
public void onEntryClick(View view, int position) {
openAnotherActivity(mArrayList.get(position))
}
});
}
I'm not sure it can help you or not but I think at least it can resolve the context
problem
Upvotes: 0
Reputation: 8938
The problem is likely related to your anonymous OnClickListener
capturing the getActivity()
method from the original fragment. You could try implementing the OnClickListener
on your MyFragment
and see if the behavior changes at all (might not be that simple):
public static class MyFragment extends Fragment implements View.OnClickListener {
...
private void doStuff() {
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), MyActivity.class);
intent.putExtra("INFORMATION", value);
startActivity(intent);
}
...
}
Edit: This is a hack and not something I would generally recommend but may get you around the problem if you're in a pinch...
Extend the Application
to provide a static application instance:
public class MyApplication extends Application {
private static MyApplication _instance;
@Override
public void onCreate() {
super.onCreate();
_instance = this;
}
public static MyApplication getInstance() {
return _instance;
}
}
Modify your AndroidManifest.xml so your application runs the MyApplication
:
<application
android:name="com.package.MyApplication"
<!-- Rest of your manifest -->
Then replace getActivity()
with a call to MyApplication.getInstance()
:
private void doStuff() {
...
mArrayList = ...;
MyRVAdapter adapter = new MyRVAdapter(getActivity(), mArrayList);
adapter.setOnEntryClickListener(new MyRVAdapter.OnEntryClickListener() {
@Override
public void onEntryClick(View view, int position) {
Intent intent = new Intent(MyApplication.getInstance(), MyActivity.class);
intent.putExtra("INFORMATION", mArrayList.get(position));
startActivity(intent);
}
});
}
Upvotes: 0