6

I have an Activity, which itself has three Fragments.

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.

Farbod Salamat-Zadeh
  • 19,687
  • 20
  • 75
  • 125
  • 2
    Likely the `getActivity()` is returning null. Where are you setting your `OnClickListener`? – Cory Charlton Jan 19 '16 at 21:51
  • please add java code and - not necessary - the problem is in code – piotrek1543 Jan 19 '16 at 21:52
  • It almost sounds like you are using the same Fragment, but with a new Activity, so when it goes to get the Activity reference, it's no longer there. Are you using a retained Fragment by any chance, or are you passing a Fragment reference to the new instance of the Activity? We'll probably need to see some code to make any sense of this. – NoChinDeluxe Jan 19 '16 at 22:07
  • What I think it occurring is that the `OnClickListener` is encapsulating the original fragment. Without seeing where the `OnClickListener` is set though I can't really say for certain. – Cory Charlton Jan 19 '16 at 22:09
  • @CoryCharlton The `OnClickListener` is called in a `Fragment` in my `Activity`. – Farbod Salamat-Zadeh Jan 19 '16 at 23:24
  • @FarbodSalamat-Zadeh understood, but where? `onCreate`? `onCreateView`? Edit your post to include the code where you are setting the `OnClickListener` and we might be able to diagnose further. – Cory Charlton Jan 19 '16 at 23:26
  • @CoryCharlton Fair point - see my updated answer. – Farbod Salamat-Zadeh Jan 19 '16 at 23:34
  • @FarbodSalamat-Zadeh see my answer below. Might not be that simple but it's a simple test :) – Cory Charlton Jan 19 '16 at 23:43
  • @MsYvette See my updated answer. – Farbod Salamat-Zadeh Jan 25 '16 at 21:56
  • Post the code related to Fragment creation from Activity. By the why, why did you declare 'static' the fragment class? – Mimmo Grottoli Jan 26 '16 at 08:22
  • @MimmoGrottoli Sure, I'll post my answer when I get home (at school now). I declared the fragment `static` because that's how I learnt to use them. – Farbod Salamat-Zadeh Jan 26 '16 at 08:49
  • @MimmoGrottoli I've updated my answer. – Farbod Salamat-Zadeh Jan 26 '16 at 18:47
  • Did you try to replace getActivity() by MyActivity.this ? I think you are now on the context of MyRVAdapter.OnEntryClickListener, so sometimes AndroidOs will not return the value for getActivity() as you expected. And additional for a question that do you have any specific configuration for you Activity in the Manifest? – ThaiPD Jan 26 '16 at 19:00
  • @PhanDinhThai I have tried that, but `MyActivity.this` cannot be referenced from a static context. – Farbod Salamat-Zadeh Jan 26 '16 at 19:05
  • There are many things to review in your code. But I would start from the click listener. Could you consider to use this [utility class](http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/) in order to implement the click listener on the items? Probably there is something to fix inside the fragment but you should disclose more code. – Mimmo Grottoli Jan 26 '16 at 22:29
  • @MimmoGrottoli There's nothing else in my fragment that would be related to this - do you have anything in particular in mind? I had also updated my answer to show how the fragments are created. I would rather not use a utilities library, because the `OnClickListener` in the `RecyclerView` `Adapter ` does the same thing. I might try including the `OnClickListener` code in the adapter itself rather than in the fragment, but I doubt this would make a difference. My suspicion is that the problem lies at `getActivity()`, but then again, I'm the one asking the question... ;) – Farbod Salamat-Zadeh Jan 26 '16 at 23:09

6 Answers6

13

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.

Mimmo Grottoli
  • 5,758
  • 2
  • 17
  • 27
  • Thank you so much! This solved my problem, but I also had to remove `static` from my `ViewHolder` as well. I also read up on this particular issue of static inner classes, and found [a great answer](http://stackoverflow.com/a/10968689/4230345) about memory leaks and static/inner classes. – Farbod Salamat-Zadeh Jan 27 '16 at 20:20
  • can you please help me to fix this error https://stackoverflow.com/questions/51405397/cannot-solve-attempt-to-invoke-virtual-method-java-lang-string-android-content @MimmoGrottoli – Zhu Jul 18 '18 at 15:14
  • @FarbodSalamat-Zadeh actually there is no reason that you would need to remove `static` from your `Viewholder` declaration. A `Viewholder` should be a `static class`, ie: just a data container with no behaviour (methods). If you are adding methods to a `Viewholder` that force you to remove the `static` modifier from the `class` definition, it means that each and every `Viewholder` object now has a reference to the outer class (in this case, the adapter). This is a waste of storage and means that your architecture is flawed. Just sayin' – David Wasser Jul 18 '18 at 17:20
0

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);
        }
    });
}
Cory Charlton
  • 8,868
  • 4
  • 48
  • 68
  • Thanks for the answer. To be honest, for me right now it's almost midnight, so I'll get some sleep and test it out tomorrow. ;) – Farbod Salamat-Zadeh Jan 19 '16 at 23:47
  • Your solution didn't work for me. However, I mentioned in my answer that I have buttons which, when clicked, open the Activity. But I'm actually using a `RecyclerView` with a custom adapter I made, and the Activity is opened when I click on an item from that. The issue may be in the adapter because when I tested something what I had before with three buttons instead of the RecyclerView and adapter, it worked fine. The reason I didn't mention it before is that I assumed there would not be an issue with the adapter, so not including it would have made a simpler question. – Farbod Salamat-Zadeh Jan 20 '16 at 18:17
  • I've updated my answer, I'm not sure if it helps, but I thought I might as well include the custom adapter if the problem is there. – Farbod Salamat-Zadeh Jan 20 '16 at 18:29
  • The problem likely is, sort of, there. Where do you set your `OnEntryClickListener`? – Cory Charlton Jan 20 '16 at 19:52
  • Sorry - see my updated answer (I forgot to include it previously). – Farbod Salamat-Zadeh Jan 20 '16 at 20:06
  • Ok so then I assume that when you said "Your solution didn't work for me." that means that you tried to implement `OnEntryClickListener` on the fragment and replaced the anonymous method with `this` in your `adapter.setOnEntryClickListener()` in `doStuff()? – Cory Charlton Jan 20 '16 at 20:19
  • Ok I provided a hack that might work as I've got nothing at this point :) – Cory Charlton Jan 20 '16 at 20:28
  • That solved that particular error, but resulted in a different one: `java.lang.IllegalStateException: Fragment MyFragment{18a42f07} not attached to Activity`. – Farbod Salamat-Zadeh Jan 20 '16 at 20:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101224/discussion-between-cory-charlton-and-farbod-salamat-zadeh). – Cory Charlton Jan 20 '16 at 20:51
0

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

ThaiPD
  • 3,503
  • 3
  • 30
  • 48
  • I can't reference `openAnotherActivity` (non-static context) from a static context. Also, is `MyApplication.getInstance()` meant to be from the other answer. – Farbod Salamat-Zadeh Jan 26 '16 at 19:39
  • So I think the problem is the static context. Why do you need to make the Holder as the static class while it can be in a normal context – ThaiPD Jan 27 '16 at 03:08
0

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);
                }
            }
        });
    }

    ...

}
Suhaib Roomy
  • 2,501
  • 1
  • 16
  • 22
0

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);
0

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.

David Buck
  • 3,752
  • 35
  • 31
  • 35
Mumbaso21
  • 1
  • 1